/* * Copyright (C) 2011 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.p2p; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.MacAddress; import android.net.wifi.OuiKeyedData; import android.net.wifi.ParcelUtil; import android.net.wifi.WpsInfo; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; import com.android.wifi.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.PatternSyntaxException; /** * A class representing a Wi-Fi P2p configuration for setting up a connection * * {@see WifiP2pManager} */ public class WifiP2pConfig implements Parcelable { /** * The device MAC address uniquely identifies a Wi-Fi p2p device */ public String deviceAddress = ""; /** * Wi-Fi Protected Setup information */ public WpsInfo wps; /** Get the network name of this P2P configuration, or null if unset. */ @Nullable public String getNetworkName() { return networkName; } /** @hide */ public String networkName = ""; /** Get the passphrase of this P2P configuration, or null if unset. */ @Nullable public String getPassphrase() { return passphrase; } /** @hide */ public String passphrase = ""; /** * Get the required band for the group owner. * The result will be one of the following: * {@link #GROUP_OWNER_BAND_AUTO}, * {@link #GROUP_OWNER_BAND_2GHZ}, * {@link #GROUP_OWNER_BAND_5GHZ} */ @GroupOperatingBandType public int getGroupOwnerBand() { return groupOwnerBand; } /** @hide */ @GroupOperatingBandType public int groupOwnerBand = GROUP_OWNER_BAND_AUTO; /** @hide */ @IntDef(flag = false, prefix = { "GROUP_OWNER_BAND_" }, value = { GROUP_OWNER_BAND_AUTO, GROUP_OWNER_BAND_2GHZ, GROUP_OWNER_BAND_5GHZ }) @Retention(RetentionPolicy.SOURCE) public @interface GroupOperatingBandType {} /** * IP provisioning via IPv4 DHCP, when joining a group as a group client. */ public static final int GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP = 0; /** * IP provisioning via IPv6 link-local, when joining a group as a group client. */ public static final int GROUP_CLIENT_IP_PROVISIONING_MODE_IPV6_LINK_LOCAL = 1; /** * Allow the system to pick the operating frequency from all supported bands. */ public static final int GROUP_OWNER_BAND_AUTO = 0; /** * Allow the system to pick the operating frequency from the 2.4 GHz band. */ public static final int GROUP_OWNER_BAND_2GHZ = 1; /** * Allow the system to pick the operating frequency from the 5 GHz band. */ public static final int GROUP_OWNER_BAND_5GHZ = 2; /** * The least inclination to be a group owner, to be filled in the field * {@link #groupOwnerIntent}. */ public static final int GROUP_OWNER_INTENT_MIN = 0; /** * The most inclination to be a group owner, to be filled in the field * {@link #groupOwnerIntent}. */ public static final int GROUP_OWNER_INTENT_MAX = 15; /** * The system can choose an appropriate owner intent value, to be filled in the field * {@link #groupOwnerIntent}. */ public static final int GROUP_OWNER_INTENT_AUTO = -1; /** * This is an integer value between {@link #GROUP_OWNER_INTENT_MIN} and * {@link #GROUP_OWNER_INTENT_MAX} where * {@link #GROUP_OWNER_INTENT_MIN} indicates the least inclination to be a group owner and * {@link #GROUP_OWNER_INTENT_MAX} indicates the highest inclination to be a group owner. * * A value of {@link #GROUP_OWNER_INTENT_AUTO} indicates the system can choose an appropriate * value. * * By default this field is set to {@link #GROUP_OWNER_INTENT_AUTO}. */ @IntRange(from = 0, to = 15) public int groupOwnerIntent = GROUP_OWNER_INTENT_AUTO; /** @hide */ @IntDef(prefix = { "GROUP_CLIENT_IP_PROVISIONING_MODE_" }, value = { GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP, GROUP_CLIENT_IP_PROVISIONING_MODE_IPV6_LINK_LOCAL }) @Retention(RetentionPolicy.SOURCE) public @interface GroupClientIpProvisioningMode {} @GroupClientIpProvisioningMode private int mGroupClientIpProvisioningMode = GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP; /** * Query whether or not join existing group is enabled/disabled. * @see #setJoinExistingGroup(boolean) * * @return true if configured to trigger the join existing group logic. False otherwise. * @hide */ @SystemApi public boolean isJoinExistingGroup() { return mJoinExistingGroup; } /** * Join an existing group as a client. */ private boolean mJoinExistingGroup = false; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int netId = WifiP2pGroup.NETWORK_ID_PERSISTENT; /** * Get the network ID of this P2P configuration. * @return either a non-negative network ID, or one of * {@link WifiP2pGroup#NETWORK_ID_PERSISTENT} or {@link WifiP2pGroup#NETWORK_ID_TEMPORARY}. */ public int getNetworkId() { return netId; } /** List of {@link OuiKeyedData} providing vendor-specific configuration data. */ private @NonNull List mVendorData = Collections.emptyList(); /** * Set additional vendor-provided configuration data. * * @param vendorData List of {@link android.net.wifi.OuiKeyedData} containing the * vendor-provided configuration data. Note that multiple elements with * the same OUI are allowed. * @hide */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) @SystemApi public void setVendorData(@NonNull List vendorData) { if (!SdkLevel.isAtLeastV()) { throw new UnsupportedOperationException(); } if (vendorData == null) { throw new IllegalArgumentException("setVendorData received a null value"); } mVendorData = new ArrayList<>(vendorData); } /** * Return the vendor-provided configuration data, if it exists. See also {@link * #setVendorData(List)} * * @return Vendor configuration data, or empty list if it does not exist. * @hide */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) @SystemApi @NonNull public List getVendorData() { if (!SdkLevel.isAtLeastV()) { throw new UnsupportedOperationException(); } return mVendorData; } public WifiP2pConfig() { //set defaults wps = new WpsInfo(); wps.setup = WpsInfo.PBC; } /** @hide */ public void invalidate() { deviceAddress = ""; } /** P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 {@hide}*/ @UnsupportedAppUsage public WifiP2pConfig(String supplicantEvent) throws IllegalArgumentException { String[] tokens = supplicantEvent.split(" "); if (tokens.length < 2 || !tokens[0].equals("P2P-GO-NEG-REQUEST")) { throw new IllegalArgumentException("Malformed supplicant event"); } deviceAddress = tokens[1]; wps = new WpsInfo(); if (tokens.length > 2) { String[] nameVal = tokens[2].split("="); int devPasswdId; try { devPasswdId = Integer.parseInt(nameVal[1]); } catch (NumberFormatException e) { devPasswdId = 0; } //Based on definitions in wps/wps_defs.h switch (devPasswdId) { //DEV_PW_USER_SPECIFIED = 0x0001, case 0x01: wps.setup = WpsInfo.DISPLAY; break; //DEV_PW_PUSHBUTTON = 0x0004, case 0x04: wps.setup = WpsInfo.PBC; break; //DEV_PW_REGISTRAR_SPECIFIED = 0x0005 case 0x05: wps.setup = WpsInfo.KEYPAD; break; default: wps.setup = WpsInfo.PBC; break; } } } /** * Get the IP provisioning mode when joining a group as a group client. * The result will be one of the following: * {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP}, * {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV6_LINK_LOCAL} */ @GroupClientIpProvisioningMode public int getGroupClientIpProvisioningMode() { return mGroupClientIpProvisioningMode; } public String toString() { StringBuffer sbuf = new StringBuffer(); sbuf.append("\n address: ").append(deviceAddress); sbuf.append("\n wps: ").append(wps); sbuf.append("\n groupOwnerIntent: ").append(groupOwnerIntent); sbuf.append("\n persist: ").append(netId); sbuf.append("\n networkName: ").append(networkName); sbuf.append("\n passphrase: ").append( TextUtils.isEmpty(passphrase) ? "" : ""); sbuf.append("\n groupOwnerBand: ").append(groupOwnerBand); sbuf.append("\n groupClientIpProvisioningMode: ").append(mGroupClientIpProvisioningMode); sbuf.append("\n joinExistingGroup: ").append(mJoinExistingGroup); sbuf.append("\n vendorData: ").append(mVendorData); return sbuf.toString(); } /** Implement the Parcelable interface */ public int describeContents() { return 0; } /** copy constructor */ public WifiP2pConfig(WifiP2pConfig source) { if (source != null) { deviceAddress = source.deviceAddress; wps = new WpsInfo(source.wps); groupOwnerIntent = source.groupOwnerIntent; netId = source.netId; networkName = source.networkName; passphrase = source.passphrase; groupOwnerBand = source.groupOwnerBand; mGroupClientIpProvisioningMode = source.mGroupClientIpProvisioningMode; mJoinExistingGroup = source.mJoinExistingGroup; mVendorData = new ArrayList<>(source.mVendorData); } } /** Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { dest.writeString(deviceAddress); dest.writeParcelable(wps, flags); dest.writeInt(groupOwnerIntent); dest.writeInt(netId); dest.writeString(networkName); dest.writeString(passphrase); dest.writeInt(groupOwnerBand); dest.writeInt(mGroupClientIpProvisioningMode); dest.writeBoolean(mJoinExistingGroup); dest.writeList(mVendorData); } /** Implement the Parcelable interface */ @NonNull public static final Creator CREATOR = new Creator() { public WifiP2pConfig createFromParcel(Parcel in) { WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = in.readString(); config.wps = (WpsInfo) in.readParcelable(null); config.groupOwnerIntent = in.readInt(); config.netId = in.readInt(); config.networkName = in.readString(); config.passphrase = in.readString(); config.groupOwnerBand = in.readInt(); config.mGroupClientIpProvisioningMode = in.readInt(); config.mJoinExistingGroup = in.readBoolean(); config.mVendorData = ParcelUtil.readOuiKeyedDataList(in); return config; } public WifiP2pConfig[] newArray(int size) { return new WifiP2pConfig[size]; } }; /** * Builder used to build {@link WifiP2pConfig} objects for * creating or joining a group. * * The WifiP2pConfig can be constructed for two use-cases: *
    *
  • SSID + Passphrase are known: use {@link #setNetworkName(String)} and * {@link #setPassphrase(String)}.
  • *
  • SSID or Passphrase is unknown, in such a case the MAC address must be known and * specified using {@link #setDeviceAddress(MacAddress)}.
  • *
*/ public static final class Builder { private static final MacAddress MAC_ANY_ADDRESS = MacAddress.fromString("02:00:00:00:00:00"); /** * Maximum number of bytes allowed for a SSID. */ private static final int MAX_SSID_BYTES = 32; private MacAddress mDeviceAddress = MAC_ANY_ADDRESS; private String mNetworkName = ""; private String mPassphrase = ""; private int mGroupOperatingBand = GROUP_OWNER_BAND_AUTO; private int mGroupOperatingFrequency = GROUP_OWNER_BAND_AUTO; private int mNetId = WifiP2pGroup.NETWORK_ID_TEMPORARY; private int mGroupClientIpProvisioningMode = GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP; private boolean mJoinExistingGroup = false; /** * Specify the peer's MAC address. If not set, the device will * try to find a peer whose SSID matches the network name as * specified by {@link #setNetworkName(String)}. Specifying null will * reset the peer's MAC address to "02:00:00:00:00:00". *

* Optional. "02:00:00:00:00:00" by default. * *

If the network name is not set, the peer's MAC address is mandatory. * * @param deviceAddress the peer's MAC address. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder setDeviceAddress(@Nullable MacAddress deviceAddress) { if (deviceAddress == null) { mDeviceAddress = MAC_ANY_ADDRESS; } else { mDeviceAddress = deviceAddress; } return this; } /** * Specify the network name, a.k.a. group name, * for creating or joining a group. *

* A network name shall begin with "DIRECT-xy". x and y are selected * from the following character set: upper case letters, lower case * letters and numbers. Any byte values allowed for an SSID according to * IEEE802.11-2012 [1] may be included after the string "DIRECT-xy" * (including none). *

* Must be called - an empty network name or an network name * not conforming to the P2P Group ID naming rule is not valid. * * @param networkName network name of a group. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder setNetworkName(@NonNull String networkName) { if (TextUtils.isEmpty(networkName)) { throw new IllegalArgumentException( "network name must be non-empty."); } if (networkName.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) { throw new IllegalArgumentException( "network name exceeds " + MAX_SSID_BYTES + " bytes."); } try { if (!networkName.matches("^DIRECT-[a-zA-Z0-9]{2}.*")) { throw new IllegalArgumentException( "network name must starts with the prefix DIRECT-xy."); } } catch (PatternSyntaxException e) { // can never happen (fixed pattern) } mNetworkName = networkName; return this; } /** * Specify the passphrase for creating or joining a group. *

* The passphrase must be an ASCII string whose length is between 8 * and 63. *

* Must be called - an empty passphrase is not valid. * * @param passphrase the passphrase of a group. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder setPassphrase(@NonNull String passphrase) { if (TextUtils.isEmpty(passphrase)) { throw new IllegalArgumentException( "passphrase must be non-empty."); } if (passphrase.length() < 8 || passphrase.length() > 63) { throw new IllegalArgumentException( "The length of a passphrase must be between 8 and 63."); } mPassphrase = passphrase; return this; } /** * Specify the band to use for creating the group or joining the group. The band should * be {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ} or * {@link #GROUP_OWNER_BAND_AUTO}. *

* When creating a group as Group Owner using {@link * WifiP2pManager#createGroup(WifiP2pManager.Channel, * WifiP2pConfig, WifiP2pManager.ActionListener)}, * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to pick the operating * frequency from all supported bands. * Specifying {@link #GROUP_OWNER_BAND_2GHZ} or {@link #GROUP_OWNER_BAND_5GHZ} * only allows the system to pick the operating frequency in the specified band. * If the Group Owner cannot create a group in the specified band, the operation will fail. *

* When joining a group as Group Client using {@link * WifiP2pManager#connect(WifiP2pManager.Channel, WifiP2pConfig, * WifiP2pManager.ActionListener)}, * specifying {@link #GROUP_OWNER_BAND_AUTO} allows the system to scan all supported * frequencies to find the desired group. Specifying {@link #GROUP_OWNER_BAND_2GHZ} or * {@link #GROUP_OWNER_BAND_5GHZ} only allows the system to scan the specified band. *

* {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are * mutually exclusive. Setting operating band and frequency both is invalid. *

* Optional. {@link #GROUP_OWNER_BAND_AUTO} by default. * * @param band the operating band of the group. * This should be one of {@link #GROUP_OWNER_BAND_AUTO}, * {@link #GROUP_OWNER_BAND_2GHZ}, {@link #GROUP_OWNER_BAND_5GHZ}. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder setGroupOperatingBand(@GroupOperatingBandType int band) { switch (band) { case GROUP_OWNER_BAND_AUTO: case GROUP_OWNER_BAND_2GHZ: case GROUP_OWNER_BAND_5GHZ: mGroupOperatingBand = band; break; default: throw new IllegalArgumentException( "Invalid constant for the group operating band!"); } return this; } /** * Specify the frequency, in MHz, to use for creating the group or joining the group. *

* When creating a group as Group Owner using {@link WifiP2pManager#createGroup( * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)}, * specifying a frequency only allows the system to pick the specified frequency. * If the Group Owner cannot create a group at the specified frequency, * the operation will fail. * When not specifying a frequency, it allows the system to pick operating frequency * from all supported bands. *

* When joining a group as Group Client using {@link WifiP2pManager#connect( * WifiP2pManager.Channel, WifiP2pConfig, WifiP2pManager.ActionListener)}, * specifying a frequency only allows the system to scan the specified frequency. * If the frequency is not supported or invalid, the operation will fail. * When not specifying a frequency, it allows the system to scan all supported * frequencies to find the desired group. *

* {@link #setGroupOperatingBand(int)} and {@link #setGroupOperatingFrequency(int)} are * mutually exclusive. Setting operating band and frequency both is invalid. *

* Optional. 0 by default. * * @param frequency the operating frequency of the group. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder setGroupOperatingFrequency(int frequency) { if (frequency < 0) { throw new IllegalArgumentException( "Invalid group operating frequency!"); } mGroupOperatingFrequency = frequency; return this; } /** * Specify that the group configuration be persisted (i.e. saved). * By default the group configuration will not be saved. *

* Optional. false by default. * * @param persistent is this group persistent group. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. */ @NonNull public Builder enablePersistentMode(boolean persistent) { if (persistent) { mNetId = WifiP2pGroup.NETWORK_ID_PERSISTENT; } else { mNetId = WifiP2pGroup.NETWORK_ID_TEMPORARY; } return this; } /** * Specify the IP provisioning mode when joining a group as a group client. The IP * provisioning mode should be {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP} or * {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV6_LINK_LOCAL}. *

* When joining a group as group client using {@link * WifiP2pManager#connect(WifiP2pManager.Channel, WifiP2pConfig, * WifiP2pManager.ActionListener)}, * specifying {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP} directs the system to * assign a IPv4 to the group client using DHCP. Specifying * {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV6_LINK_LOCAL} directs the system to assign * a link-local IPv6 to the group client. *

* Optional. {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP} by default. *

* * If {@link WifiP2pManager#isGroupOwnerIPv6LinkLocalAddressProvided()} is {@code true} and * {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV6_LINK_LOCAL} is used then the system will * discover the group owner's IPv6 link-local address and broadcast it using the * {@link WifiP2pManager#EXTRA_WIFI_P2P_INFO} extra of the * {@link WifiP2pManager#WIFI_P2P_CONNECTION_CHANGED_ACTION} broadcast. Otherwise, if * {@link WifiP2pManager#isGroupOwnerIPv6LinkLocalAddressProvided()} is * {@code false} then the group owner's IPv6 link-local address is not discovered and it is * the responsibility of the caller to obtain it in some other way, e.g. via out-of-band * communication. * * @param groupClientIpProvisioningMode the IP provisioning mode of the group client. * This should be one of {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP}, * {@link #GROUP_CLIENT_IP_PROVISIONING_MODE_IPV6_LINK_LOCAL}. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. * @see WifiP2pManager#isGroupOwnerIPv6LinkLocalAddressProvided() */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) @NonNull public Builder setGroupClientIpProvisioningMode( @GroupClientIpProvisioningMode int groupClientIpProvisioningMode) { // Since group client IP provisioning modes use NetworkStack functionalities introduced // in T, hence we need at least T sdk for this to be supported. if (!SdkLevel.isAtLeastT()) { throw new UnsupportedOperationException( "IPv6 link-local provisioning not supported"); } switch (groupClientIpProvisioningMode) { case GROUP_CLIENT_IP_PROVISIONING_MODE_IPV4_DHCP: case GROUP_CLIENT_IP_PROVISIONING_MODE_IPV6_LINK_LOCAL: mGroupClientIpProvisioningMode = groupClientIpProvisioningMode; break; default: throw new IllegalArgumentException( "Invalid constant for the group client IP provisioning mode!"); } return this; } /** * Specify that the device wants to join an existing group as client. * Usually group owner sets the group owner capability bit in beacons/probe responses. But * there are deployed devices which don't set the group owner capability bit. * This API is for applications which can get the peer group owner capability via OOB * (out of band) mechanisms and forcefully trigger the join existing group logic. *

* Optional. false by default. * * @param join true to forcefully trigger the join existing group logic, false to let * device decide whether to join a group or form a group. * @return The builder to facilitate chaining * {@code builder.setXXX(..).setXXX(..)}. * @hide */ @SystemApi @NonNull public Builder setJoinExistingGroup(boolean join) { mJoinExistingGroup = join; return this; } /** * Build {@link WifiP2pConfig} given the current requests made on the builder. * @return {@link WifiP2pConfig} constructed based on builder method calls. */ @NonNull public WifiP2pConfig build() { if ((TextUtils.isEmpty(mNetworkName) && !TextUtils.isEmpty(mPassphrase)) || (!TextUtils.isEmpty(mNetworkName) && TextUtils.isEmpty(mPassphrase))) { throw new IllegalStateException( "network name and passphrase must be non-empty or empty both."); } if (TextUtils.isEmpty(mNetworkName) && mDeviceAddress.equals(MAC_ANY_ADDRESS)) { throw new IllegalStateException( "peer address must be set if network name and pasphrase are not set."); } if (mGroupOperatingFrequency > 0 && mGroupOperatingBand > 0) { throw new IllegalStateException( "Preferred frequency and band are mutually exclusive."); } WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = mDeviceAddress.toString(); config.networkName = mNetworkName; config.passphrase = mPassphrase; config.groupOwnerBand = GROUP_OWNER_BAND_AUTO; if (mGroupOperatingFrequency > 0) { config.groupOwnerBand = mGroupOperatingFrequency; } else if (mGroupOperatingBand > 0) { config.groupOwnerBand = mGroupOperatingBand; } config.netId = mNetId; config.mGroupClientIpProvisioningMode = mGroupClientIpProvisioningMode; config.mJoinExistingGroup = mJoinExistingGroup; return config; } } }