That's amazing!! I remember the struggle. What do you think changed?
Hahaha Josh I was literally about to forward this to you On Thu, Mar 4, 2021 at 1:45 PM Josh King via guardian-dev < [email protected]> wrote: > That is wild. *shakes fist* ISSUE 80! (I think it was 80, right?) > > On Thu, 2021-03-04 at 16:06 +0100, Hans-Christoph Steiner wrote: > > > > Many years ago, quite a few of us tried very hard to get Google to > > accept > > patches to enable Support Ad-Hoc (IBSS) WiFi in Android. They > > ignored us... > > until now? It seems that an Android team member is working on it > > now: > > > > > > -------- Forwarded Message -------- > > Subject: Change in aosp/base[master]: frameworks/base: Support Ad-Hoc > > (IBSS) > > WiFi networks > > Date: Thu, 4 Mar 2021 05:26:13 -0800 > > From: gwsq (Gerrit) > > <[email protected]> > > Reply-To: [email protected], [email protected], > > [email protected], > > [email protected], [email protected], [email protected], > > [email protected] > > To: Etan Cohen <[email protected]> > > CC: Bruno Randolf <[email protected]>, Deckard Autoverifier > > <[email protected]>, > > Hans of Guardian <[email protected]>, Irfan Sheriff > > <[email protected]>, Romain Guy <[email protected]>, Robert > > Greenwalt > > <[email protected]> > > > > Attention is currently required from: Etan Cohen. > > Hello Etan Cohen, > > > > I'd like you to do a code review. Please visit > > > > > > > https://android-review.googlesource.com/c/platform/frameworks/base/+/88040 > > > > to review the following change. > > > > > > Change subject: frameworks/base: Support Ad-Hoc (IBSS) WiFi networks > > ..................................................................... > > . > > > > frameworks/base: Support Ad-Hoc (IBSS) WiFi networks > > > > This adds framework support for Ad-Hoc (IBSS) networks. > > > > - Add two public variables to WifiConfiguration: isIBSS and frequency > > which > > are used for configuring IBSS mode. WifiConfigStore is exended to > > get and set > > these variables to wpa_supplicant via WifiNative. > > > > - Add method isIbssSupported() to WifiManager and related classes to > > query > > wpa_supplicant wether the driver supports IBSS mode. > > > > - Get the list of supported channels/frequencies from wpa_supplicant > > and > > provide them thru WifiManager.getSupportedChannels(); > > One channel is represented thru the class WifiChannel which > > includes channel > > number, frequency in MHz and a flag if IBSS mode is allowed on > > this channel. > > > > This adds new public API to WifiManager, WifiConfiguration and a new > > class > > WifiChannel. The WifiManager methods can only return a valid result > > after > > WIFI_STATE_ENABLED has been reached. > > > > Change-Id: Id4ebfd28cfd96df1f70f2f5bd35390d65dcb0c1f > > --- > > M api/current.txt > > M services/java/com/android/server/wifi/WifiService.java > > M wifi/java/android/net/wifi/IWifiManager.aidl > > A wifi/java/android/net/wifi/WifiChannel.aidl > > A wifi/java/android/net/wifi/WifiChannel.java > > M wifi/java/android/net/wifi/WifiConfigStore.java > > M wifi/java/android/net/wifi/WifiConfiguration.java > > M wifi/java/android/net/wifi/WifiManager.java > > M wifi/java/android/net/wifi/WifiNative.java > > M wifi/java/android/net/wifi/WifiStateMachine.java > > 10 files changed, 317 insertions(+), 0 deletions(-) > > > > > > > > diff --git a/api/current.txt b/api/current.txt > > index a98fcfb..1c25360 100644 > > --- a/api/current.txt > > +++ b/api/current.txt > > @@ -14497,6 +14497,18 @@ > > enum_constant public static final > > android.net.wifi.SupplicantState > > UNINITIALIZED; > > } > > > > + public class WifiChannel implements android.os.Parcelable { > > + ctor public WifiChannel(); > > + ctor public WifiChannel(int, int, boolean); > > + ctor public WifiChannel(android.net.wifi.WifiChannel); > > + method public int describeContents(); > > + method public void writeToParcel(android.os.Parcel, int); > > + field public static final android.os.Parcelable.Creator CREATOR; > > + field public int channel; > > + field public int frequency; > > + field public boolean ibssAllowed; > > + } > > + > > public class WifiConfiguration implements android.os.Parcelable > > { > > ctor public WifiConfiguration(); > > method public int describeContents(); > > @@ -14510,6 +14522,8 @@ > > field public java.util.BitSet allowedProtocols; > > field public android.net.wifi.WifiEnterpriseConfig > > enterpriseConfig; > > field public boolean hiddenSSID; > > + field public boolean isIBSS; > > + field public int frequency; > > field public int networkId; > > field public java.lang.String preSharedKey; > > field public int priority; > > @@ -14636,9 +14650,11 @@ > > method public android.net.wifi.WifiInfo getConnectionInfo(); > > method public android.net.DhcpInfo getDhcpInfo(); > > method public java.util.List<android.net.wifi.ScanResult> > > getScanResults(); > > + method public java.util.List<android.net.wifi.WifiChannel> > > getSupportedChannels(); > > method public int getWifiState(); > > method public boolean isScanAlwaysAvailable(); > > method public boolean isWifiEnabled(); > > + method public boolean isIbssSupported(); > > method public boolean pingSupplicant(); > > method public boolean reassociate(); > > method public boolean reconnect(); > > diff --git a/services/java/com/android/server/wifi/WifiService.java > > b/services/java/com/android/server/wifi/WifiService.java > > index f2efde1..774181e 100644 > > --- a/services/java/com/android/server/wifi/WifiService.java > > +++ b/services/java/com/android/server/wifi/WifiService.java > > @@ -36,6 +36,7 @@ > > import android.net.wifi.BatchedScanSettings; > > import android.net.wifi.WifiConfiguration; > > import android.net.wifi.WifiConfiguration.ProxySettings; > > +import android.net.wifi.WifiChannel; > > import android.net.wifi.WifiInfo; > > import android.net.wifi.WifiManager; > > import android.net.wifi.WifiStateMachine; > > @@ -917,6 +918,31 @@ > > } > > > > /** > > + * Is Ad-Hoc (IBSS) mode supported by the driver? > > + * Will only return correct results when we have reached > > WIFI_STATE_ENABLED > > + * @return {@code true} if IBSS mode is supported, {@code false} > > if not > > + */ > > + public boolean isIbssSupported() { > > + enforceAccessPermission(); > > + if (mWifiStateMachineChannel != null) { > > + return > > (mWifiStateMachine.syncIsIbssSupported(mWifiStateMachineChannel) == > > 1); > > + } else { > > + Slog.e(TAG, "mWifiStateMachineChannel is not > > initialized"); > > + return false; > > + } > > + } > > + > > + public List<WifiChannel> getSupportedChannels() { > > + enforceAccessPermission(); > > + if (mWifiStateMachineChannel != null) { > > + return > > (mWifiStateMachine.syncGetSupportedChannels(mWifiStateMachineChannel) > > ); > > + } else { > > + Slog.e(TAG, "mWifiStateMachineChannel is not > > initialized"); > > + return null; > > + } > > + } > > + > > + /** > > * Return the DHCP-assigned addresses from the last successful > > DHCP request, > > * if any. > > * @return the DHCP information > > diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl > > b/wifi/java/android/net/wifi/IWifiManager.aidl > > index 149bda6..7ba7f2a 100644 > > --- a/wifi/java/android/net/wifi/IWifiManager.aidl > > +++ b/wifi/java/android/net/wifi/IWifiManager.aidl > > @@ -21,6 +21,7 @@ > > import android.net.wifi.WifiConfiguration; > > import android.net.wifi.WifiInfo; > > import android.net.wifi.ScanResult; > > +import android.net.wifi.WifiChannel; > > import android.net.DhcpInfo; > > > > import android.os.Messenger; > > @@ -69,6 +70,10 @@ > > > > boolean isDualBandSupported(); > > > > + boolean isIbssSupported(); > > + > > + List<WifiChannel> getSupportedChannels(); > > + > > boolean saveConfiguration(); > > > > DhcpInfo getDhcpInfo(); > > diff --git a/wifi/java/android/net/wifi/WifiChannel.aidl > > b/wifi/java/android/net/wifi/WifiChannel.aidl > > new file mode 100644 > > index 0000000..e12def3 > > --- /dev/null > > +++ b/wifi/java/android/net/wifi/WifiChannel.aidl > > @@ -0,0 +1,19 @@ > > +/** > > + * Copyright (c) 2013, The Android Open Source Project > > + * > > + * Licensed under the Apache License, Version 2.0 (the "License"); > > + * you may not use this file except in compliance with the License. > > + * You may obtain a copy of the License at > > + * > > + * http://www.apache.org/licenses/LICENSE-2.0 > > + * > > + * Unless required by applicable law or agreed to in writing, > > software > > + * distributed under the License is distributed on an "AS IS" BASIS, > > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or > > implied. > > + * See the License for the specific language governing permissions > > and > > + * limitations under the License. > > + */ > > + > > +package android.net.wifi; > > + > > +parcelable WifiChannel; > > diff --git a/wifi/java/android/net/wifi/WifiChannel.java > > b/wifi/java/android/net/wifi/WifiChannel.java > > new file mode 100644 > > index 0000000..f1b1291 > > --- /dev/null > > +++ b/wifi/java/android/net/wifi/WifiChannel.java > > @@ -0,0 +1,84 @@ > > + > > +package android.net.wifi; > > + > > +import android.os.Parcel; > > +import android.os.Parcelable; > > + > > +/** > > + * A class representing one wifi channel or frequency > > + */ > > +public class WifiChannel implements Parcelable { > > + public int channel; > > + public int frequency; > > + public boolean ibssAllowed; > > + > > + public String toString() { > > + StringBuffer sbuf = new StringBuffer(); > > + sbuf.append(" channel: ").append(channel); > > + sbuf.append(" freq: ").append(frequency); > > + sbuf.append(" MHz"); > > + sbuf.append(" IBSS: ").append(ibssAllowed ? "allowed" : > > "not allowed"); > > + sbuf.append('\n'); > > + return sbuf.toString(); > > + } > > + > > + @Override > > + public boolean equals(Object o) { > > + if (o instanceof WifiChannel) { > > + WifiChannel w = (WifiChannel)o; > > + return (this.channel == w.channel && > > + this.frequency == w.frequency && > > + this.ibssAllowed == w.ibssAllowed); > > + } > > + return false; > > + } > > + > > + /** Implement the Parcelable interface */ > > + public int describeContents() { > > + return 0; > > + } > > + > > + public WifiChannel() { > > + channel = 0; > > + frequency = 0; > > + ibssAllowed = false; > > + } > > + > > + public WifiChannel(int ch, int freq, boolean ibss) { > > + channel = ch; > > + frequency = freq; > > + ibssAllowed = ibss; > > + } > > + > > + /* Copy constructor */ > > + public WifiChannel(WifiChannel source) { > > + if (source != null) { > > + channel = source.channel; > > + frequency = source.frequency; > > + ibssAllowed = source.ibssAllowed; > > + } > > + } > > + > > + /** Implement the Parcelable interface */ > > + public void writeToParcel(Parcel dest, int flags) { > > + dest.writeInt(channel); > > + dest.writeInt(frequency); > > + dest.writeInt(ibssAllowed ? 1 : 0); > > + } > > + > > + /** Implement the Parcelable interface */ > > + public static final Creator<WifiChannel> CREATOR = > > + new Creator<WifiChannel>() { > > + public WifiChannel createFromParcel(Parcel in) { > > + WifiChannel ch = new WifiChannel(); > > + ch.channel = in.readInt(); > > + ch.frequency = in.readInt(); > > + ch.ibssAllowed = (in.readInt() == 1); > > + return ch; > > + } > > + > > + public WifiChannel[] newArray(int size) { > > + return new WifiChannel[size]; > > + } > > + }; > > +} > > diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java > > b/wifi/java/android/net/wifi/WifiConfigStore.java > > index a6ae215..9f6e8da 100644 > > --- a/wifi/java/android/net/wifi/WifiConfigStore.java > > +++ b/wifi/java/android/net/wifi/WifiConfigStore.java > > @@ -1116,6 +1116,23 @@ > > break setVariables; > > } > > > > + if (config.isIBSS) { > > + if(!mWifiNative.setNetworkVariable( > > + netId, > > + WifiConfiguration.modeVarName, > > + "1")) { > > + loge("failed to set adhoc mode"); > > + break setVariables; > > + } > > + if(!mWifiNative.setNetworkVariable( > > + netId, > > + WifiConfiguration.frequencyVarName, > > + Integer.toString(config.frequency))) { > > + loge("failed to set frequency"); > > + break setVariables; > > + } > > + } > > + > > String allowedKeyManagementString = > > makeString(config.allowedKeyManagement, > > WifiConfiguration.KeyMgmt.strings); > > if (config.allowedKeyManagement.cardinality() != 0 && > > @@ -1494,6 +1511,24 @@ > > } > > } > > > > + value = mWifiNative.getNetworkVariable(netId, > > WifiConfiguration.modeVarName); > > + config.isIBSS = false; > > + if (!TextUtils.isEmpty(value)) { > > + try { > > + config.isIBSS = Integer.parseInt(value) != 0; > > + } catch (NumberFormatException ignore) { > > + } > > + } > > + > > + value = mWifiNative.getNetworkVariable(netId, > > WifiConfiguration.frequencyVarName); > > + config.frequency = 0; > > + if (!TextUtils.isEmpty(value)) { > > + try { > > + config.frequency = Integer.parseInt(value); > > + } catch (NumberFormatException ignore) { > > + } > > + } > > + > > value = mWifiNative.getNetworkVariable(netId, > > WifiConfiguration.wepTxKeyIdxVarName); > > config.wepTxKeyIndex = -1; > > if (!TextUtils.isEmpty(value)) { > > diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java > > b/wifi/java/android/net/wifi/WifiConfiguration.java > > index 87afa88..406208a 100644 > > --- a/wifi/java/android/net/wifi/WifiConfiguration.java > > +++ b/wifi/java/android/net/wifi/WifiConfiguration.java > > @@ -44,6 +44,10 @@ > > /** {@hide} */ > > public static final String hiddenSSIDVarName = "scan_ssid"; > > /** {@hide} */ > > + public static final String modeVarName = "mode"; > > + /** {@hide} */ > > + public static final String frequencyVarName = "frequency"; > > + /** {@hide} */ > > public static final int INVALID_NETWORK_ID = -1; > > /** > > * Recognized key management schemes. > > @@ -246,6 +250,16 @@ > > */ > > public boolean hiddenSSID; > > > > + /** > > + * This is an Ad-Hoc (IBSS) network > > + */ > > + public boolean isIBSS; > > + > > + /** > > + * Frequency of the Ad-Hoc (IBSS) network, if newly created > > + */ > > + public int frequency; > > + > > /** > > * The set of key management protocols supported by this > > configuration. > > * See {@link KeyMgmt} for descriptions of the values. > > @@ -332,6 +346,8 @@ > > BSSID = null; > > priority = 0; > > hiddenSSID = false; > > + isIBSS = false; > > + frequency = 0; > > disableReason = DISABLED_UNKNOWN_REASON; > > allowedKeyManagement = new BitSet(); > > allowedProtocols = new BitSet(); > > @@ -593,6 +609,8 @@ > > wepTxKeyIndex = source.wepTxKeyIndex; > > priority = source.priority; > > hiddenSSID = source.hiddenSSID; > > + isIBSS = source.isIBSS; > > + frequency = source.frequency; > > allowedKeyManagement = (BitSet) > > source.allowedKeyManagement.clone(); > > allowedProtocols = (BitSet) > > source.allowedProtocols.clone(); > > allowedAuthAlgorithms = (BitSet) > > source.allowedAuthAlgorithms.clone(); > > @@ -621,6 +639,8 @@ > > dest.writeInt(wepTxKeyIndex); > > dest.writeInt(priority); > > dest.writeInt(hiddenSSID ? 1 : 0); > > + dest.writeInt(isIBSS ? 1 : 0); > > + dest.writeInt(frequency); > > > > writeBitSet(dest, allowedKeyManagement); > > writeBitSet(dest, allowedProtocols); > > @@ -652,6 +672,8 @@ > > config.wepTxKeyIndex = in.readInt(); > > config.priority = in.readInt(); > > config.hiddenSSID = in.readInt() != 0; > > + config.isIBSS = in.readInt() != 0; > > + config.frequency = in.readInt(); > > config.allowedKeyManagement = readBitSet(in); > > config.allowedProtocols = readBitSet(in); > > config.allowedAuthAlgorithms = readBitSet(in); > > diff --git a/wifi/java/android/net/wifi/WifiManager.java > > b/wifi/java/android/net/wifi/WifiManager.java > > index 3b47e63..a6a7192 100644 > > --- a/wifi/java/android/net/wifi/WifiManager.java > > +++ b/wifi/java/android/net/wifi/WifiManager.java > > @@ -991,6 +991,30 @@ > > } > > > > /** > > + * Check if the chipset supports IBSS (Adhoc) mode > > + * @return {@code true} if supported, {@code false} otherwise. > > + */ > > + public boolean isIbssSupported() { > > + try { > > + return mService.isIbssSupported(); > > + } catch (RemoteException e) { > > + return false; > > + } > > + } > > + > > + /** > > + * Get a list of supported channels / frequencies > > + * @return a List of WifiChannels > > + */ > > + public List<WifiChannel> getSupportedChannels() { > > + try { > > + return mService.getSupportedChannels(); > > + } catch (RemoteException e) { > > + return null; > > + } > > + } > > + > > + /** > > * Return the DHCP-assigned addresses from the last successful > > DHCP request, > > * if any. > > * @return the DHCP information > > diff --git a/wifi/java/android/net/wifi/WifiNative.java > > b/wifi/java/android/net/wifi/WifiNative.java > > index c2f278a..bb22f76 100644 > > --- a/wifi/java/android/net/wifi/WifiNative.java > > +++ b/wifi/java/android/net/wifi/WifiNative.java > > @@ -967,4 +967,50 @@ > > // Note: optional feature on the driver. It is ok for this > > to fail. > > doBooleanCommand("DRIVER MIRACAST " + mode); > > } > > + > > + public boolean getModeCapability(String mode) { > > + String ret = doStringCommand("GET_CAPABILITY modes"); > > + if (!TextUtils.isEmpty(ret)) { > > + String[] tokens = ret.split(" "); > > + for (String t : tokens) { > > + if (t.compareTo(mode) == 0) { > > + return true; > > + } > > + } > > + } > > + return false; > > + } > > + > > + public List<WifiChannel> getSupportedChannels() { > > + boolean ibssAllowed; > > + List<WifiChannel> channels = new ArrayList<WifiChannel>(); > > + String ret = doStringCommand("GET_CAPABILITY freq"); > > + > > + if (!TextUtils.isEmpty(ret)) { > > + String[] lines = ret.split("\n"); > > + for (String l : lines) { > > + if (l.startsWith("Mode") || TextUtils.isEmpty(l)) > > continue; > > + > > + String[] tokens = l.split(" "); > > + if (tokens.length < 4) continue; > > + > > + if (tokens.length == 6 && > > tokens[5].contains("NO_IBSS")) { > > + ibssAllowed = false; > > + } else { > > + ibssAllowed = true; > > + } > > + > > + try { > > + WifiChannel ch = new > > WifiChannel(Integer.parseInt(tokens[1]), > > + Integer.parseInt(tokens[3]), > > ibssAllowed); > > + if (!channels.contains(ch)) { > > + channels.add(ch); > > + } > > + } catch (java.lang.NumberFormatException e) { > > + Log.d(mTAG, "Can't parse: " + l); > > + } > > + } > > + } > > + return channels; > > + } > > } > > diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java > > b/wifi/java/android/net/wifi/WifiStateMachine.java > > index 69d3efe..ad6d03a 100644 > > --- a/wifi/java/android/net/wifi/WifiStateMachine.java > > +++ b/wifi/java/android/net/wifi/WifiStateMachine.java > > @@ -119,6 +119,7 @@ > > private ConnectivityManager mCm; > > > > private final boolean mP2pSupported; > > + private boolean mIbssSupported; > > private final AtomicBoolean mP2pConnected = new > > AtomicBoolean(false); > > private boolean mTemporarilyDisconnectWifi = false; > > private final String mPrimaryDeviceType; > > @@ -168,6 +169,8 @@ > > > > private PowerManager.WakeLock mSuspendWakeLock; > > > > + private List<WifiChannel> mSupportedChannels; > > + > > /** > > * Interval in milliseconds between polling for RSSI > > * and linkspeed information > > @@ -433,6 +436,11 @@ > > /* Reload all networks and reconnect */ > > static final int CMD_RELOAD_TLS_AND_RECONNECT = BASE + > > 142; > > > > + /* Is IBSS mode supported by the driver? */ > > + public static final int CMD_GET_IBSS_SUPPORTED = BASE + > > 143; > > + /* Get supported channels */ > > + public static final int CMD_GET_SUPPORTED_CHANNELS = BASE + > > 144; > > + > > /* Wifi state machine modes of operation */ > > /* CONNECT_MODE - connect to any 'known' AP when it becomes > > available */ > > public static final int CONNECT_MODE = 1; > > @@ -1545,6 +1553,20 @@ > > > > mWifiP2pChannel.sendMessage(WifiP2pService.SET_COUNTRY_CODE, > > countryCode); > > } > > > > + public int syncIsIbssSupported(AsyncChannel channel) { > > + Message resultMsg = > > channel.sendMessageSynchronously(CMD_GET_IBSS_SUPPORTED); > > + int result = resultMsg.arg1; > > + resultMsg.recycle(); > > + return result; > > + } > > + > > + public List<WifiChannel> syncGetSupportedChannels(AsyncChannel > > channel) { > > + Message resultMsg = > > channel.sendMessageSynchronously(CMD_GET_SUPPORTED_CHANNELS); > > + List<WifiChannel> result = (List<WifiChannel>) > > resultMsg.obj; > > + resultMsg.recycle(); > > + return result; > > + } > > + > > /** > > * Set the operational frequency band > > * @param band > > @@ -2480,8 +2502,12 @@ > > case CMD_ADD_OR_UPDATE_NETWORK: > > case CMD_REMOVE_NETWORK: > > case CMD_SAVE_CONFIG: > > + case CMD_GET_IBSS_SUPPORTED: > > replyToMessage(message, message.what, > > FAILURE); > > break; > > + case CMD_GET_SUPPORTED_CHANNELS: > > + replyToMessage(message, message.what, > > (List<WifiChannel>) > > null); > > + break; > > case CMD_GET_CONFIGURED_NETWORKS: > > replyToMessage(message, message.what, > > (List<WifiConfiguration>) null); > > break; > > @@ -2769,6 +2795,9 @@ > > mWifiConfigStore.loadAndEnableAllNetworks(); > > initializeWpsDetails(); > > > > + mIbssSupported = > > mWifiNative.getModeCapability("IBSS"); > > + mSupportedChannels = > > mWifiNative.getSupportedChannels(); > > + > > > > sendSupplicantConnectionChangedBroadcast(true); > > transitionTo(mDriverStartedState); > > break; > > @@ -2797,6 +2826,8 @@ > > case CMD_SET_FREQUENCY_BAND: > > case CMD_START_PACKET_FILTERING: > > case CMD_STOP_PACKET_FILTERING: > > + case CMD_GET_IBSS_SUPPORTED: > > + case CMD_GET_SUPPORTED_CHANNELS: > > deferMessage(message); > > break; > > default: > > @@ -2860,6 +2891,10 @@ > > case CMD_SET_OPERATIONAL_MODE: > > mOperationalMode = message.arg1; > > break; > > + case CMD_GET_IBSS_SUPPORTED: > > + case CMD_GET_SUPPORTED_CHANNELS: > > + deferMessage(message); > > + break; > > default: > > return NOT_HANDLED; > > } > > @@ -3213,6 +3248,11 @@ > > boolean enable = (message.arg1 == 1); > > mWifiNative.startTdls(remoteAddress, > > enable); > > } > > + case CMD_GET_IBSS_SUPPORTED: > > + replyToMessage(message, message.what, > > mIbssSupported ? 1 : 0); > > + break; > > + case CMD_GET_SUPPORTED_CHANNELS: > > + replyToMessage(message, message.what, > > mSupportedChannels); > > break; > > default: > > return NOT_HANDLED; > > > > -- > Josh King > he/him/they/them > PGP Fingerprint: 8269 ED6F EA3B 7D78 F074 1E99 2FDA 4DA1 69AE 4999 > _______________________________________________ > List info: https://lists.mayfirst.org/mailman/listinfo/guardian-dev > To unsubscribe, email: [email protected] >
_______________________________________________ List info: https://lists.mayfirst.org/mailman/listinfo/guardian-dev To unsubscribe, email: [email protected]
