summaryrefslogtreecommitdiff
path: root/wifi
diff options
context:
space:
mode:
Diffstat (limited to 'wifi')
-rw-r--r--wifi/TEST_MAPPING10
-rw-r--r--wifi/java/Android.bp5
-rw-r--r--wifi/java/src/android/net/wifi/WifiKeystore.java126
-rw-r--r--wifi/java/src/android/net/wifi/nl80211/OWNERS1
-rw-r--r--wifi/java/src/android/net/wifi/nl80211/PnoSettings.java54
-rw-r--r--wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java17
-rw-r--r--wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java124
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS4
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.aidl19
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java418
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.aidl19
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java276
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl19
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java328
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.aidl19
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java226
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.aidl19
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java359
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java92
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java592
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl19
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java203
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl34
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl41
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java467
-rw-r--r--wifi/tests/Android.bp1
-rw-r--r--wifi/tests/src/android/net/wifi/nl80211/OWNERS1
-rw-r--r--wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java8
-rw-r--r--wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java26
-rw-r--r--wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS1
-rw-r--r--wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java410
31 files changed, 3915 insertions, 23 deletions
diff --git a/wifi/TEST_MAPPING b/wifi/TEST_MAPPING
index 94e4f4d6bf13..14f5af3fd6bc 100644
--- a/wifi/TEST_MAPPING
+++ b/wifi/TEST_MAPPING
@@ -3,5 +3,15 @@
{
"name": "FrameworksWifiNonUpdatableApiTests"
}
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsWifiTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported"
+ }
+ ]
+ }
]
}
diff --git a/wifi/java/Android.bp b/wifi/java/Android.bp
index 225e750923fd..434226d75b28 100644
--- a/wifi/java/Android.bp
+++ b/wifi/java/Android.bp
@@ -27,7 +27,10 @@ package {
filegroup {
name: "framework-wifi-non-updatable-sources-internal",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
path: "src",
visibility: ["//visibility:private"],
}
diff --git a/wifi/java/src/android/net/wifi/WifiKeystore.java b/wifi/java/src/android/net/wifi/WifiKeystore.java
new file mode 100644
index 000000000000..ca86dde2fa45
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/WifiKeystore.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 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;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.security.legacykeystore.ILegacyKeystore;
+import android.util.Log;
+
+/**
+ * @hide This class allows wifi framework to store and access wifi certificate blobs.
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class WifiKeystore {
+ private static final String TAG = "WifiKeystore";
+ private static final String LEGACY_KEYSTORE_SERVICE_NAME = "android.security.legacykeystore";
+
+ private static ILegacyKeystore getService() {
+ return ILegacyKeystore.Stub.asInterface(
+ ServiceManager.checkService(LEGACY_KEYSTORE_SERVICE_NAME));
+ }
+
+ /** @hide */
+ WifiKeystore() {
+ }
+
+ /**
+ * Stores the blob under the alias in the keystore database. Existing blobs by the
+ * same name will be replaced.
+ * @param alias The name of the blob
+ * @param blob The blob.
+ * @return true if the blob was successfully added. False otherwise.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static boolean put(@NonNull String alias, @NonNull byte[] blob) {
+ try {
+ Log.i(TAG, "put blob. alias " + alias);
+ getService().put(alias, Process.WIFI_UID, blob);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to put blob.", e);
+ return false;
+ }
+ }
+
+ /**
+ * Retrieves a blob by the name alias from the blob database.
+ * @param alias Name of the blob to retrieve.
+ * @return The unstructured blob, that is the blob that was stored using
+ * {@link android.net.wifi.WifiKeystore#put}.
+ * Returns null if no blob was found.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static @NonNull byte[] get(@NonNull String alias) {
+ try {
+ Log.i(TAG, "get blob. alias " + alias);
+ return getService().get(alias, Process.WIFI_UID);
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode != ILegacyKeystore.ERROR_ENTRY_NOT_FOUND) {
+ Log.e(TAG, "Failed to get blob.", e);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to get blob.", e);
+ }
+ return null;
+ }
+
+ /**
+ * Removes a blob by the name alias from the database.
+ * @param alias Name of the blob to be removed.
+ * @return True if a blob was removed. False if no such blob was found.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static boolean remove(@NonNull String alias) {
+ try {
+ getService().remove(alias, Process.WIFI_UID);
+ return true;
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode != ILegacyKeystore.ERROR_ENTRY_NOT_FOUND) {
+ Log.e(TAG, "Failed to remove blob.", e);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to remove blob.", e);
+ }
+ return false;
+ }
+
+ /**
+ * Lists the blobs stored in the database.
+ * @return An array of strings representing the aliases stored in the database.
+ * The return value may be empty but never null.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static @NonNull String[] list(@NonNull String prefix) {
+ try {
+ final String[] aliases = getService().list(prefix, Process.WIFI_UID);
+ for (int i = 0; i < aliases.length; ++i) {
+ aliases[i] = aliases[i].substring(prefix.length());
+ }
+ return aliases;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to list blobs.", e);
+ }
+ return new String[0];
+ }
+} \ No newline at end of file
diff --git a/wifi/java/src/android/net/wifi/nl80211/OWNERS b/wifi/java/src/android/net/wifi/nl80211/OWNERS
new file mode 100644
index 000000000000..8a75e25cb2f6
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/nl80211/OWNERS
@@ -0,0 +1 @@
+kumachang@google.com
diff --git a/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java b/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java
index 00ebe624ba0d..2f15066f97cf 100644
--- a/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java
+++ b/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java
@@ -19,9 +19,12 @@ package android.net.wifi.nl80211;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.RequiresApi;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -38,6 +41,8 @@ public final class PnoSettings implements Parcelable {
private int mMin2gRssi;
private int mMin5gRssi;
private int mMin6gRssi;
+ private int mScanIterations;
+ private int mScanIntervalMultiplier;
private List<PnoNetwork> mPnoNetworks;
/** Construct an uninitialized PnoSettings object */
@@ -122,6 +127,46 @@ public final class PnoSettings implements Parcelable {
}
/**
+ * Get the requested PNO scan iterations.
+ *
+ * @return PNO scan iterations.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public int getScanIterations() {
+ return mScanIterations;
+ }
+
+ /**
+ * Set the requested PNO scan iterations.
+ *
+ * @param scanIterations the PNO scan iterations.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void setScanIterations(int scanIterations) {
+ this.mScanIterations = scanIterations;
+ }
+
+ /**
+ * Get the requested PNO scan interval multiplier.
+ *
+ * @return PNO scan interval multiplier.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public int getScanIntervalMultiplier() {
+ return mScanIntervalMultiplier;
+ }
+
+ /**
+ * Set the requested PNO scan interval multiplier.
+ *
+ * @param scanIntervalMultiplier the PNO scan interval multiplier.
+ */
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void setScanIntervalMultiplier(int scanIntervalMultiplier) {
+ this.mScanIntervalMultiplier = scanIntervalMultiplier;
+ }
+
+ /**
* Return the configured list of specific networks to search for in a PNO scan.
*
* @return A list of {@link PnoNetwork} objects, possibly empty if non configured.
@@ -156,13 +201,16 @@ public final class PnoSettings implements Parcelable {
&& mMin2gRssi == settings.mMin2gRssi
&& mMin5gRssi == settings.mMin5gRssi
&& mMin6gRssi == settings.mMin6gRssi
+ && mScanIterations == settings.mScanIterations
+ && mScanIntervalMultiplier == settings.mScanIntervalMultiplier
&& mPnoNetworks.equals(settings.mPnoNetworks);
}
/** override hash code */
@Override
public int hashCode() {
- return Objects.hash(mIntervalMs, mMin2gRssi, mMin5gRssi, mMin6gRssi, mPnoNetworks);
+ return Objects.hash(mIntervalMs, mMin2gRssi, mMin5gRssi, mMin6gRssi,
+ mScanIterations, mScanIntervalMultiplier, mPnoNetworks);
}
/** implement Parcelable interface */
@@ -181,6 +229,8 @@ public final class PnoSettings implements Parcelable {
out.writeInt(mMin2gRssi);
out.writeInt(mMin5gRssi);
out.writeInt(mMin6gRssi);
+ out.writeInt(mScanIterations);
+ out.writeInt(mScanIntervalMultiplier);
out.writeTypedList(mPnoNetworks);
}
@@ -194,6 +244,8 @@ public final class PnoSettings implements Parcelable {
result.mMin2gRssi = in.readInt();
result.mMin5gRssi = in.readInt();
result.mMin6gRssi = in.readInt();
+ result.mScanIterations = in.readInt();
+ result.mScanIntervalMultiplier = in.readInt();
result.mPnoNetworks = new ArrayList<>();
in.readTypedList(result.mPnoNetworks, PnoNetwork.CREATOR);
diff --git a/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java b/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java
index 1d479fc14d29..4a821bbcd54d 100644
--- a/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java
+++ b/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java
@@ -21,6 +21,7 @@ import android.os.Parcelable;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -35,6 +36,7 @@ public class SingleScanSettings implements Parcelable {
public boolean enable6GhzRnr;
public ArrayList<ChannelSettings> channelSettings;
public ArrayList<HiddenNetwork> hiddenNetworks;
+ public byte[] vendorIes;
/** public constructor */
public SingleScanSettings() { }
@@ -53,13 +55,15 @@ public class SingleScanSettings implements Parcelable {
return scanType == settings.scanType
&& enable6GhzRnr == settings.enable6GhzRnr
&& channelSettings.equals(settings.channelSettings)
- && hiddenNetworks.equals(settings.hiddenNetworks);
+ && hiddenNetworks.equals(settings.hiddenNetworks)
+ && Arrays.equals(vendorIes, settings.vendorIes);
}
/** override hash code */
@Override
public int hashCode() {
- return Objects.hash(scanType, channelSettings, hiddenNetworks, enable6GhzRnr);
+ return Objects.hash(scanType, channelSettings, hiddenNetworks, enable6GhzRnr,
+ Arrays.hashCode(vendorIes));
}
@@ -88,6 +92,11 @@ public class SingleScanSettings implements Parcelable {
out.writeBoolean(enable6GhzRnr);
out.writeTypedList(channelSettings);
out.writeTypedList(hiddenNetworks);
+ if (vendorIes == null) {
+ out.writeByteArray(new byte[0]);
+ } else {
+ out.writeByteArray(vendorIes);
+ }
}
/** implement Parcelable interface */
@@ -108,6 +117,10 @@ public class SingleScanSettings implements Parcelable {
in.readTypedList(result.channelSettings, ChannelSettings.CREATOR);
result.hiddenNetworks = new ArrayList<HiddenNetwork>();
in.readTypedList(result.hiddenNetworks, HiddenNetwork.CREATOR);
+ result.vendorIes = in.createByteArray();
+ if (result.vendorIes == null) {
+ result.vendorIes = new byte[0];
+ }
if (in.dataAvail() != 0) {
Log.e(TAG, "Found trailing data after parcel parsing.");
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index d85a5bdc3e66..2a199d27a60e 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -96,6 +96,10 @@ public class WifiNl80211Manager {
public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR =
"android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR";
+ // Extra scanning parameter used to add vendor IEs (byte[]).
+ public static final String EXTRA_SCANNING_PARAM_VENDOR_IES =
+ "android.net.wifi.nl80211.extra.SCANNING_PARAM_VENDOR_IES";
+
private AlarmManager mAlarmManager;
private Handler mEventHandler;
@@ -137,9 +141,17 @@ public class WifiNl80211Manager {
void onScanResultReady();
/**
+ * Deprecated in Android 14. Newer wificond implementation should call
+ * onScanRequestFailed().
* Called when a scan has failed.
+ * @deprecated The usage is replaced by {@link ScanEventCallback#onScanFailed(int)}
*/
+
void onScanFailed();
+ /**
+ * Called when a scan has failed with errorCode.
+ */
+ default void onScanFailed(int errorCode) {}
}
/**
@@ -230,11 +242,27 @@ public class WifiNl80211Manager {
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void OnScanRequestFailed(int errorCode) {
+ Log.d(TAG, "Scan failed event with error code: " + errorCode);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onScanFailed(
+ toFrameworkScanStatusCode(errorCode)));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
/**
* Result of a signal poll requested using {@link #signalPoll(String)}.
+ *
+ * @deprecated The usage is replaced by
+ * {@code com.android.server.wifi.WifiSignalPollResults}.
*/
+ @Deprecated
public static class SignalPollResult {
/** @hide */
public SignalPollResult(int currentRssiDbm, int txBitrateMbps, int rxBitrateMbps,
@@ -393,6 +421,21 @@ public class WifiNl80211Manager {
mEventHandler = new Handler(context.getMainLooper());
}
+ /**
+ * Construct WifiNl80211Manager with giving context and binder which is an interface of
+ * IWificond.
+ *
+ * @param context Android context.
+ * @param binder a binder of IWificond.
+ */
+ public WifiNl80211Manager(@NonNull Context context, @NonNull IBinder binder) {
+ this(context);
+ mWificond = IWificond.Stub.asInterface(binder);
+ if (mWificond == null) {
+ Log.e(TAG, "Failed to get reference to wificond");
+ }
+ }
+
/** @hide */
@VisibleForTesting
public WifiNl80211Manager(Context context, IWificond wificond) {
@@ -861,7 +904,11 @@ public class WifiNl80211Manager {
*
* @return A {@link SignalPollResult} object containing interface statistics, or a null on
* error (e.g. the interface hasn't been set up yet).
+ *
+ * @deprecated replaced by
+ * {@link com.android.server.wifi.SupplicantStaIfaceHal#getSignalPollResults}
*/
+ @Deprecated
@Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) {
IClientInterface iface = getClientInterface(ifaceName);
if (iface == null) {
@@ -1007,6 +1054,32 @@ public class WifiNl80211Manager {
}
/**
+ * @deprecated replaced by {@link #startScan2(String, int, Set, List, Bundle)}
+ */
+ @Deprecated
+ public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
+ @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
+ @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
+ @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
+ return false;
+ }
+ SingleScanSettings settings = createSingleScanSettings(scanType, freqs, hiddenNetworkSSIDs,
+ extraScanningParams);
+ if (settings == null) {
+ return false;
+ }
+ try {
+ return scannerImpl.scan(settings);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request scan due to remote exception");
+ }
+ return false;
+ }
+
+ /**
* Start a scan using the specified parameters. A scan is an asynchronous operation. The
* result of the operation is returned in the {@link ScanEventCallback} registered when
* setting up an interface using
@@ -1026,29 +1099,47 @@ public class WifiNl80211Manager {
* @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that
* no hidden frequencies will be scanned for.
* @param extraScanningParams bundle of extra scanning parameters.
- * @return Returns true on success, false on failure (e.g. when called before the interface
- * has been set up).
+ * @return Returns one of the scan status codes defined in {@code WifiScanner#REASON_*}
*/
- public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
+ public int startScan2(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
@SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
@SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
@SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
- return false;
+ return WifiScanner.REASON_INVALID_ARGS;
+ }
+ SingleScanSettings settings = createSingleScanSettings(scanType, freqs, hiddenNetworkSSIDs,
+ extraScanningParams);
+ if (settings == null) {
+ return WifiScanner.REASON_INVALID_ARGS;
}
+ try {
+ int status = scannerImpl.scanRequest(settings);
+ return toFrameworkScanStatusCode(status);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request scan due to remote exception");
+ }
+ return WifiScanner.REASON_UNSPECIFIED;
+ }
+
+ private SingleScanSettings createSingleScanSettings(@WifiAnnotations.ScanType int scanType,
+ @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
+ @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
+ @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
SingleScanSettings settings = new SingleScanSettings();
try {
settings.scanType = getScanType(scanType);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid scan type ", e);
- return false;
+ return null;
}
settings.channelSettings = new ArrayList<>();
settings.hiddenNetworks = new ArrayList<>();
if (extraScanningParams != null) {
settings.enable6GhzRnr = extraScanningParams.getBoolean(SCANNING_PARAM_ENABLE_6GHZ_RNR);
+ settings.vendorIes = extraScanningParams.getByteArray(EXTRA_SCANNING_PARAM_VENDOR_IES);
}
if (freqs != null) {
@@ -1071,12 +1162,25 @@ public class WifiNl80211Manager {
}
}
- try {
- return scannerImpl.scan(settings);
- } catch (RemoteException e1) {
- Log.e(TAG, "Failed to request scan due to remote exception");
+ return settings;
+ }
+
+ private int toFrameworkScanStatusCode(int scanStatus) {
+ switch(scanStatus) {
+ case IWifiScannerImpl.SCAN_STATUS_SUCCESS:
+ return WifiScanner.REASON_SUCCEEDED;
+ case IWifiScannerImpl.SCAN_STATUS_FAILED_BUSY:
+ return WifiScanner.REASON_BUSY;
+ case IWifiScannerImpl.SCAN_STATUS_FAILED_ABORT:
+ return WifiScanner.REASON_ABORT;
+ case IWifiScannerImpl.SCAN_STATUS_FAILED_NODEV:
+ return WifiScanner.REASON_NO_DEVICE;
+ case IWifiScannerImpl.SCAN_STATUS_FAILED_INVALID_ARGS:
+ return WifiScanner.REASON_INVALID_ARGS;
+ case IWifiScannerImpl.SCAN_STATUS_FAILED_GENERIC:
+ default:
+ return WifiScanner.REASON_UNSPECIFIED;
}
- return false;
}
/**
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS b/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
new file mode 100644
index 000000000000..2a4acc111257
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1216021
+
+asapperstein@google.com
+etancohen@google.com
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.aidl
new file mode 100644
index 000000000000..f9c4829c8f93
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+parcelable HotspotNetwork;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java
new file mode 100644
index 000000000000..fe397d9c2662
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+import static android.net.wifi.WifiAnnotations.SecurityType;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A data class representing a hotspot network.
+ * This class is used in IPC calls between the implementer of {@link SharedConnectivityService} and
+ * the consumers of {@link com.android.wifitrackerlib}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class HotspotNetwork implements Parcelable {
+ /**
+ * Remote device is connected to the internet via an unknown connection.
+ */
+ public static final int NETWORK_TYPE_UNKNOWN = 0;
+
+ /**
+ * Remote device is connected to the internet via a cellular connection.
+ */
+ public static final int NETWORK_TYPE_CELLULAR = 1;
+
+ /**
+ * Remote device is connected to the internet via a Wi-Fi connection.
+ */
+ public static final int NETWORK_TYPE_WIFI = 2;
+
+ /**
+ * Remote device is connected to the internet via an ethernet connection.
+ */
+ public static final int NETWORK_TYPE_ETHERNET = 3;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ NETWORK_TYPE_UNKNOWN,
+ NETWORK_TYPE_CELLULAR,
+ NETWORK_TYPE_WIFI,
+ NETWORK_TYPE_ETHERNET
+ })
+ public @interface NetworkType {
+ }
+
+ private final long mDeviceId;
+ private final NetworkProviderInfo mNetworkProviderInfo;
+ @NetworkType
+ private final int mNetworkType;
+ private final String mNetworkName;
+ @Nullable
+ private final String mHotspotSsid;
+ @Nullable
+ private final String mHotspotBssid;
+ @Nullable
+ @SecurityType
+ private final ArraySet<Integer> mHotspotSecurityTypes;
+ private final Bundle mExtras;
+
+ /**
+ * Builder class for {@link HotspotNetwork}.
+ */
+ public static final class Builder {
+ private long mDeviceId = -1;
+ private NetworkProviderInfo mNetworkProviderInfo;
+ @NetworkType
+ private int mNetworkType;
+ private String mNetworkName;
+ @Nullable
+ private String mHotspotSsid;
+ @Nullable
+ private String mHotspotBssid;
+ @Nullable
+ @SecurityType
+ private final ArraySet<Integer> mHotspotSecurityTypes = new ArraySet<>();
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Set the remote device ID.
+ *
+ * @param deviceId Locally unique ID for this Hotspot network.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setDeviceId(long deviceId) {
+ mDeviceId = deviceId;
+ return this;
+ }
+
+ /**
+ * Sets information about the device providing connectivity.
+ *
+ * @param networkProviderInfo The device information object.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setNetworkProviderInfo(@NonNull NetworkProviderInfo networkProviderInfo) {
+ mNetworkProviderInfo = networkProviderInfo;
+ return this;
+ }
+
+ /**
+ * Sets the network type that the remote device is connected to.
+ *
+ * @param networkType Network type as represented by IntDef {@link NetworkType}.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setHostNetworkType(@NetworkType int networkType) {
+ mNetworkType = networkType;
+ return this;
+ }
+
+ /**
+ * Sets the display name of the network the remote device is connected to.
+ *
+ * @param networkName Network display name. (e.g. "Google Fi", "Hotel WiFi", "Ethernet")
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setNetworkName(@NonNull String networkName) {
+ mNetworkName = networkName;
+ return this;
+ }
+
+ /**
+ * Sets the hotspot SSID being broadcast by the remote device, or null if hotspot is off.
+ *
+ * @param hotspotSsid The SSID of the hotspot. Surrounded by double quotes if UTF-8.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setHotspotSsid(@NonNull String hotspotSsid) {
+ mHotspotSsid = hotspotSsid;
+ return this;
+ }
+
+ /**
+ * Sets the hotspot BSSID being broadcast by the remote device, or null if hotspot is off.
+ *
+ * @param hotspotBssid The BSSID of the hotspot.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setHotspotBssid(@NonNull String hotspotBssid) {
+ mHotspotBssid = hotspotBssid;
+ return this;
+ }
+
+ /**
+ * Adds a security type supported by the hotspot created by the remote device.
+ *
+ * @param hotspotSecurityType A security type supported by the hotspot.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder addHotspotSecurityType(@SecurityType int hotspotSecurityType) {
+ mHotspotSecurityTypes.add(hotspotSecurityType);
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link HotspotNetwork} object.
+ *
+ * @return Returns the built {@link HotspotNetwork} object.
+ */
+ @NonNull
+ public HotspotNetwork build() {
+ return new HotspotNetwork(
+ mDeviceId,
+ mNetworkProviderInfo,
+ mNetworkType,
+ mNetworkName,
+ mHotspotSsid,
+ mHotspotBssid,
+ mHotspotSecurityTypes,
+ mExtras);
+ }
+ }
+
+ private static void validate(long deviceId, @NetworkType int networkType, String networkName,
+ NetworkProviderInfo networkProviderInfo) {
+ if (deviceId < 0) {
+ throw new IllegalArgumentException("DeviceId must be set");
+ }
+ if (Objects.isNull(networkProviderInfo)) {
+ throw new IllegalArgumentException("NetworkProviderInfo must be set");
+ }
+ if (networkType != NETWORK_TYPE_CELLULAR && networkType != NETWORK_TYPE_WIFI
+ && networkType != NETWORK_TYPE_ETHERNET && networkType != NETWORK_TYPE_UNKNOWN) {
+ throw new IllegalArgumentException("Illegal network type");
+ }
+ if (Objects.isNull(networkName)) {
+ throw new IllegalArgumentException("NetworkName must be set");
+ }
+ }
+
+ private HotspotNetwork(
+ long deviceId,
+ NetworkProviderInfo networkProviderInfo,
+ @NetworkType int networkType,
+ @NonNull String networkName,
+ @Nullable String hotspotSsid,
+ @Nullable String hotspotBssid,
+ @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes,
+ @NonNull Bundle extras) {
+ validate(deviceId,
+ networkType,
+ networkName,
+ networkProviderInfo);
+ mDeviceId = deviceId;
+ mNetworkProviderInfo = networkProviderInfo;
+ mNetworkType = networkType;
+ mNetworkName = networkName;
+ mHotspotSsid = hotspotSsid;
+ mHotspotBssid = hotspotBssid;
+ mHotspotSecurityTypes = new ArraySet<>(hotspotSecurityTypes);
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the remote device ID.
+ *
+ * @return Returns the locally unique ID for this Hotspot network.
+ */
+ public long getDeviceId() {
+ return mDeviceId;
+ }
+
+ /**
+ * Gets information about the device providing connectivity.
+ *
+ * @return Returns the information of the device providing the Hotspot network.
+ */
+ @NonNull
+ public NetworkProviderInfo getNetworkProviderInfo() {
+ return mNetworkProviderInfo;
+ }
+
+ /**
+ * Gets the network type that the remote device is connected to.
+ *
+ * @return Returns the network type as represented by IntDef {@link NetworkType}.
+ */
+ @NetworkType
+ public int getHostNetworkType() {
+ return mNetworkType;
+ }
+
+ /**
+ * Gets the display name of the network the remote device is connected to.
+ *
+ * @return Returns the network display name. (e.g. "Google Fi", "Hotel WiFi", "Ethernet")
+ */
+ @NonNull
+ public String getNetworkName() {
+ return mNetworkName;
+ }
+
+ /**
+ * Gets the hotspot SSID being broadcast by the remote device, or null if hotspot is off.
+ *
+ * @return Returns the SSID of the hotspot. Surrounded by double quotes if UTF-8.
+ */
+ @Nullable
+ public String getHotspotSsid() {
+ return mHotspotSsid;
+ }
+
+ /**
+ * Gets the hotspot BSSID being broadcast by the remote device, or null if hotspot is off.
+ *
+ * @return Returns the BSSID of the hotspot.
+ */
+ @Nullable
+ public String getHotspotBssid() {
+ return mHotspotBssid;
+ }
+
+ /**
+ * Gets the hotspot security types supported by the remote device.
+ *
+ * @return Returns a set of the security types supported by the hotspot.
+ */
+ @NonNull
+ @SecurityType
+ public Set<Integer> getHotspotSecurityTypes() {
+ return mHotspotSecurityTypes;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof HotspotNetwork)) return false;
+ HotspotNetwork other = (HotspotNetwork) obj;
+ return mDeviceId == other.getDeviceId()
+ && Objects.equals(mNetworkProviderInfo, other.getNetworkProviderInfo())
+ && mNetworkType == other.getHostNetworkType()
+ && Objects.equals(mNetworkName, other.getNetworkName())
+ && Objects.equals(mHotspotSsid, other.getHotspotSsid())
+ && Objects.equals(mHotspotBssid, other.getHotspotBssid())
+ && Objects.equals(mHotspotSecurityTypes, other.getHotspotSecurityTypes());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDeviceId, mNetworkProviderInfo, mNetworkName, mHotspotSsid,
+ mHotspotBssid, mHotspotSecurityTypes);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mDeviceId);
+ mNetworkProviderInfo.writeToParcel(dest, flags);
+ dest.writeInt(mNetworkType);
+ dest.writeString(mNetworkName);
+ dest.writeString(mHotspotSsid);
+ dest.writeString(mHotspotBssid);
+ dest.writeArraySet(mHotspotSecurityTypes);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * Creates a {@link HotspotNetwork} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static HotspotNetwork readFromParcel(@NonNull Parcel in) {
+ return new HotspotNetwork(in.readLong(), NetworkProviderInfo.readFromParcel(in),
+ in.readInt(), in.readString(), in.readString(), in.readString(),
+ (ArraySet<Integer>) in.readArraySet(null), in.readBundle());
+ }
+
+ @NonNull
+ public static final Creator<HotspotNetwork> CREATOR = new Creator<>() {
+ @Override
+ public HotspotNetwork createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ public HotspotNetwork[] newArray(int size) {
+ return new HotspotNetwork[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("HotspotNetwork[")
+ .append("deviceId=").append(mDeviceId)
+ .append(", networkType=").append(mNetworkType)
+ .append(", networkProviderInfo=").append(mNetworkProviderInfo.toString())
+ .append(", networkName=").append(mNetworkName)
+ .append(", hotspotSsid=").append(mHotspotSsid)
+ .append(", hotspotBssid=").append(mHotspotBssid)
+ .append(", hotspotSecurityTypes=").append(mHotspotSecurityTypes.toString())
+ .append(", extras=").append(mExtras.toString())
+ .append("]").toString();
+ }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.aidl
new file mode 100644
index 000000000000..d32d15e7c058
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+parcelable HotspotNetworkConnectionStatus;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java
new file mode 100644
index 000000000000..72acf2ccab0c
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The status of a connection to a hotspot network after the client called
+ * {@link SharedConnectivityManager#connectHotspotNetwork}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class HotspotNetworkConnectionStatus implements Parcelable {
+
+ /**
+ * Connection status is unknown.
+ */
+ public static final int CONNECTION_STATUS_UNKNOWN = 0;
+
+ /**
+ * The connection is being initiated.
+ */
+ public static final int CONNECTION_STATUS_ENABLING_HOTSPOT = 1;
+
+ /**
+ * Device providing the hotspot failed to initiate it.
+ */
+ public static final int CONNECTION_STATUS_UNKNOWN_ERROR = 2;
+
+ /**
+ * Failed to provision tethering.
+ */
+ public static final int CONNECTION_STATUS_PROVISIONING_FAILED = 3;
+
+ /**
+ * Timeout while trying to provision tethering.
+ */
+ public static final int CONNECTION_STATUS_TETHERING_TIMEOUT = 4;
+
+ /**
+ * Device doesn't support tethering.
+ */
+ public static final int CONNECTION_STATUS_TETHERING_UNSUPPORTED = 5;
+
+ /**
+ * Device has no cell data.
+ */
+ public static final int CONNECTION_STATUS_NO_CELL_DATA = 6;
+
+ /**
+ * Device failed to enable hotspot
+ */
+ public static final int CONNECTION_STATUS_ENABLING_HOTSPOT_FAILED = 7;
+
+ /**
+ * Timeout while trying to enable hotspot
+ */
+ public static final int CONNECTION_STATUS_ENABLING_HOTSPOT_TIMEOUT = 8;
+
+ /**
+ * Failed to connect to hotspot
+ */
+ public static final int CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED = 9;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ CONNECTION_STATUS_UNKNOWN,
+ CONNECTION_STATUS_ENABLING_HOTSPOT,
+ CONNECTION_STATUS_UNKNOWN_ERROR,
+ CONNECTION_STATUS_PROVISIONING_FAILED,
+ CONNECTION_STATUS_TETHERING_TIMEOUT,
+ CONNECTION_STATUS_TETHERING_UNSUPPORTED,
+ CONNECTION_STATUS_NO_CELL_DATA,
+ CONNECTION_STATUS_ENABLING_HOTSPOT_FAILED,
+ CONNECTION_STATUS_ENABLING_HOTSPOT_TIMEOUT,
+ CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED,
+ })
+ public @interface ConnectionStatus {
+ }
+
+ @ConnectionStatus
+ private final int mStatus;
+ private final HotspotNetwork mHotspotNetwork;
+ private final Bundle mExtras;
+
+ /**
+ * Builder class for {@link HotspotNetworkConnectionStatus}.
+ */
+ public static final class Builder {
+ @ConnectionStatus
+ private int mStatus;
+ private HotspotNetwork mHotspotNetwork;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the status of the connection
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setStatus(@ConnectionStatus int status) {
+ mStatus = status;
+ return this;
+ }
+
+ /**
+ * Sets the {@link HotspotNetwork} object of the connection.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setHotspotNetwork(@NonNull HotspotNetwork hotspotNetwork) {
+ mHotspotNetwork = hotspotNetwork;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link HotspotNetworkConnectionStatus} object.
+ *
+ * @return Returns the built {@link HotspotNetworkConnectionStatus} object.
+ */
+ @NonNull
+ public HotspotNetworkConnectionStatus build() {
+ return new HotspotNetworkConnectionStatus(mStatus, mHotspotNetwork, mExtras);
+ }
+ }
+
+ private static void validate(@ConnectionStatus int status) {
+ if (status != CONNECTION_STATUS_UNKNOWN
+ && status != CONNECTION_STATUS_ENABLING_HOTSPOT
+ && status != CONNECTION_STATUS_UNKNOWN_ERROR
+ && status != CONNECTION_STATUS_PROVISIONING_FAILED
+ && status != CONNECTION_STATUS_TETHERING_TIMEOUT
+ && status != CONNECTION_STATUS_TETHERING_UNSUPPORTED
+ && status != CONNECTION_STATUS_NO_CELL_DATA
+ && status != CONNECTION_STATUS_ENABLING_HOTSPOT_FAILED
+ && status != CONNECTION_STATUS_ENABLING_HOTSPOT_TIMEOUT
+ && status != CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED) {
+ throw new IllegalArgumentException("Illegal connection status");
+ }
+ }
+
+ private HotspotNetworkConnectionStatus(@ConnectionStatus int status,
+ HotspotNetwork hotspotNetwork, @NonNull Bundle extras) {
+ validate(status);
+ mStatus = status;
+ mHotspotNetwork = hotspotNetwork;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the status of the connection
+ *
+ * @return Returns true for enabled, false otherwise.
+ */
+ @ConnectionStatus
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Gets the {@link HotspotNetwork} object of the connection.
+ *
+ * @return Returns a HotspotNetwork object.
+ */
+ @NonNull
+ public HotspotNetwork getHotspotNetwork() {
+ return mHotspotNetwork;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof HotspotNetworkConnectionStatus)) return false;
+ HotspotNetworkConnectionStatus other = (HotspotNetworkConnectionStatus) obj;
+ return mStatus == other.getStatus()
+ && Objects.equals(mHotspotNetwork, other.getHotspotNetwork());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus, mHotspotNetwork);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ mHotspotNetwork.writeToParcel(dest, flags);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * Creates a {@link HotspotNetworkConnectionStatus} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static HotspotNetworkConnectionStatus readFromParcel(@NonNull Parcel in) {
+ return new HotspotNetworkConnectionStatus(in.readInt(),
+ HotspotNetwork.readFromParcel(in), in.readBundle());
+ }
+
+ @NonNull
+ public static final Creator<HotspotNetworkConnectionStatus> CREATOR = new Creator<>() {
+ @Override
+ public HotspotNetworkConnectionStatus createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ public HotspotNetworkConnectionStatus[] newArray(int size) {
+ return new HotspotNetworkConnectionStatus[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("HotspotNetworkConnectionStatus[")
+ .append("status=").append(mStatus)
+ .append("hotspot network=").append(mHotspotNetwork.toString())
+ .append("extras=").append(mExtras.toString())
+ .append("]").toString();
+ }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl
new file mode 100644
index 000000000000..140d72ace70d
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+parcelable KnownNetwork;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
new file mode 100644
index 000000000000..33f4d465abab
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+import static android.net.wifi.WifiAnnotations.SecurityType;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A data class representing a known Wi-Fi network.
+ *
+ * @hide
+ */
+@SystemApi
+public final class KnownNetwork implements Parcelable {
+
+ /**
+ * Network source is unknown.
+ */
+ public static final int NETWORK_SOURCE_UNKNOWN = 0;
+
+ /**
+ * Network is known by a nearby device with the same user account.
+ */
+ public static final int NETWORK_SOURCE_NEARBY_SELF = 1;
+
+ /**
+ * Network is known via cloud storage associated with this device's user account.
+ */
+ public static final int NETWORK_SOURCE_CLOUD_SELF = 2;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ NETWORK_SOURCE_UNKNOWN,
+ NETWORK_SOURCE_NEARBY_SELF,
+ NETWORK_SOURCE_CLOUD_SELF
+ })
+ public @interface NetworkSource {
+ }
+
+ @NetworkSource
+ private final int mNetworkSource;
+ private final String mSsid;
+ @SecurityType
+ private final ArraySet<Integer> mSecurityTypes;
+ private final NetworkProviderInfo mNetworkProviderInfo;
+ private final Bundle mExtras;
+
+ /**
+ * Builder class for {@link KnownNetwork}.
+ */
+ public static final class Builder {
+ @NetworkSource
+ private int mNetworkSource = -1;
+ private String mSsid;
+ @SecurityType
+ private final ArraySet<Integer> mSecurityTypes = new ArraySet<>();
+ private NetworkProviderInfo mNetworkProviderInfo;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the indicated source of the known network.
+ *
+ * @param networkSource The network source as defined by IntDef {@link NetworkSource}.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setNetworkSource(@NetworkSource int networkSource) {
+ mNetworkSource = networkSource;
+ return this;
+ }
+
+ /**
+ * Sets the SSID of the known network.
+ *
+ * @param ssid The SSID of the known network. Surrounded by double quotes if UTF-8.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setSsid(@NonNull String ssid) {
+ mSsid = ssid;
+ return this;
+ }
+
+ /**
+ * Adds a security type of the known network.
+ *
+ * @param securityType A security type supported by the known network.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder addSecurityType(@SecurityType int securityType) {
+ mSecurityTypes.add(securityType);
+ return this;
+ }
+
+ /**
+ * Sets the device information of the device providing connectivity.
+ * Must be set if network source is {@link KnownNetwork#NETWORK_SOURCE_NEARBY_SELF}.
+ *
+ * @param networkProviderInfo The device information object.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setNetworkProviderInfo(@Nullable NetworkProviderInfo networkProviderInfo) {
+ mNetworkProviderInfo = networkProviderInfo;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link KnownNetwork} object.
+ *
+ * @return Returns the built {@link KnownNetwork} object.
+ */
+ @NonNull
+ public KnownNetwork build() {
+ return new KnownNetwork(
+ mNetworkSource,
+ mSsid,
+ mSecurityTypes,
+ mNetworkProviderInfo,
+ mExtras);
+ }
+ }
+
+ private static void validate(@NetworkSource int networkSource, String ssid,
+ @SecurityType Set<Integer> securityTypes,
+ NetworkProviderInfo networkProviderInfo) {
+ if (networkSource != NETWORK_SOURCE_UNKNOWN
+ && networkSource != NETWORK_SOURCE_CLOUD_SELF
+ && networkSource != NETWORK_SOURCE_NEARBY_SELF) {
+ throw new IllegalArgumentException("Illegal network source");
+ }
+ if (TextUtils.isEmpty(ssid)) {
+ throw new IllegalArgumentException("SSID must be set");
+ }
+ if (securityTypes.isEmpty()) {
+ throw new IllegalArgumentException("SecurityTypes must be set");
+ }
+ if (networkSource == NETWORK_SOURCE_NEARBY_SELF && networkProviderInfo == null) {
+ throw new IllegalArgumentException("Device info must be provided when network source"
+ + " is NETWORK_SOURCE_NEARBY_SELF");
+ }
+ }
+
+ private KnownNetwork(
+ @NetworkSource int networkSource,
+ @NonNull String ssid,
+ @NonNull @SecurityType ArraySet<Integer> securityTypes,
+ @Nullable NetworkProviderInfo networkProviderInfo,
+ @NonNull Bundle extras) {
+ validate(networkSource, ssid, securityTypes, networkProviderInfo);
+ mNetworkSource = networkSource;
+ mSsid = ssid;
+ mSecurityTypes = new ArraySet<>(securityTypes);
+ mNetworkProviderInfo = networkProviderInfo;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the indicated source of the known network.
+ *
+ * @return Returns the network source as defined by IntDef {@link NetworkSource}.
+ */
+ @NetworkSource
+ public int getNetworkSource() {
+ return mNetworkSource;
+ }
+
+ /**
+ * Gets the SSID of the known network.
+ *
+ * @return Returns the SSID of the known network. Surrounded by double quotes if UTF-8.
+ */
+ @NonNull
+ public String getSsid() {
+ return mSsid;
+ }
+
+ /**
+ * Gets the security types of the known network.
+ *
+ * @return Returns a set with security types supported by the known network.
+ */
+ @NonNull
+ @SecurityType
+ public Set<Integer> getSecurityTypes() {
+ return mSecurityTypes;
+ }
+
+ /**
+ * Gets the device information of the device providing connectivity.
+ *
+ * @return Returns the information of the device providing the known network. Can be null if the
+ * network source is cloud or unknown.
+ */
+ @Nullable
+ public NetworkProviderInfo getNetworkProviderInfo() {
+ return mNetworkProviderInfo;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof KnownNetwork)) return false;
+ KnownNetwork other = (KnownNetwork) obj;
+ return mNetworkSource == other.getNetworkSource()
+ && Objects.equals(mSsid, other.getSsid())
+ && Objects.equals(mSecurityTypes, other.getSecurityTypes())
+ && Objects.equals(mNetworkProviderInfo, other.getNetworkProviderInfo());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNetworkSource, mSsid, mSecurityTypes, mNetworkProviderInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mNetworkSource);
+ dest.writeString(mSsid);
+ dest.writeArraySet(mSecurityTypes);
+ if (mNetworkProviderInfo != null) {
+ dest.writeBoolean(true);
+ mNetworkProviderInfo.writeToParcel(dest, flags);
+ } else {
+ dest.writeBoolean(false);
+ }
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * Creates a {@link KnownNetwork} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static KnownNetwork readFromParcel(@NonNull Parcel in) {
+ int networkSource = in.readInt();
+ String mSsid = in.readString();
+ ArraySet<Integer> securityTypes = (ArraySet<Integer>) in.readArraySet(null);
+ if (in.readBoolean()) {
+ return new KnownNetwork(networkSource, mSsid, securityTypes,
+ NetworkProviderInfo.readFromParcel(in), in.readBundle());
+ }
+ return new KnownNetwork(networkSource, mSsid, securityTypes, null,
+ in.readBundle());
+ }
+
+ @NonNull
+ public static final Creator<KnownNetwork> CREATOR = new Creator<>() {
+ @Override
+ public KnownNetwork createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ public KnownNetwork[] newArray(int size) {
+ return new KnownNetwork[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("KnownNetwork[")
+ .append("NetworkSource=").append(mNetworkSource)
+ .append(", ssid=").append(mSsid)
+ .append(", securityTypes=").append(mSecurityTypes.toString())
+ .append(", networkProviderInfo=").append(mNetworkProviderInfo.toString())
+ .append(", extras=").append(mExtras.toString())
+ .append("]").toString();
+ }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.aidl
new file mode 100644
index 000000000000..df43508ab0cc
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+parcelable KnownNetworkConnectionStatus;
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
new file mode 100644
index 000000000000..b30dc3f6b530
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The status of a connection to a known network after the client called
+ * {@link SharedConnectivityManager#connectKnownNetwork}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class KnownNetworkConnectionStatus implements Parcelable {
+
+ /**
+ * Connection status is unknown.
+ */
+ public static final int CONNECTION_STATUS_UNKNOWN = 0;
+
+ /**
+ * The connection's data was saved successfully in the Wi-Fi configuration.
+ */
+ public static final int CONNECTION_STATUS_SAVED = 1;
+
+ /**
+ * Failed to save the connection's data in the Wi-Fi configuration.
+ */
+ public static final int CONNECTION_STATUS_SAVE_FAILED = 2;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ CONNECTION_STATUS_UNKNOWN,
+ CONNECTION_STATUS_SAVED,
+ CONNECTION_STATUS_SAVE_FAILED,
+ })
+ public @interface ConnectionStatus {}
+
+ @ConnectionStatus private final int mStatus;
+ private final KnownNetwork mKnownNetwork;
+ private final Bundle mExtras;
+
+ /**
+ * Builder class for {@link KnownNetworkConnectionStatus}.
+ */
+ public static final class Builder {
+ @ConnectionStatus private int mStatus;
+ private KnownNetwork mKnownNetwork;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the status of the connection
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setStatus(@ConnectionStatus int status) {
+ mStatus = status;
+ return this;
+ }
+
+ /**
+ * Sets the {@link KnownNetwork} object of the connection.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setKnownNetwork(@NonNull KnownNetwork knownNetwork) {
+ mKnownNetwork = knownNetwork;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link KnownNetworkConnectionStatus} object.
+ *
+ * @return Returns the built {@link KnownNetworkConnectionStatus} object.
+ */
+ @NonNull
+ public KnownNetworkConnectionStatus build() {
+ return new KnownNetworkConnectionStatus(mStatus, mKnownNetwork, mExtras);
+ }
+ }
+
+ private static void validate(@ConnectionStatus int status) {
+ if (status != CONNECTION_STATUS_UNKNOWN && status != CONNECTION_STATUS_SAVED
+ && status != CONNECTION_STATUS_SAVE_FAILED) {
+ throw new IllegalArgumentException("Illegal connection status");
+ }
+ }
+
+ private KnownNetworkConnectionStatus(@ConnectionStatus int status, KnownNetwork knownNetwork,
+ @NonNull Bundle extras) {
+ validate(status);
+ mStatus = status;
+ mKnownNetwork = knownNetwork;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the status of the connection
+ *
+ * @return Returns true for enabled, false otherwise.
+ */
+ @ConnectionStatus
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Gets the {@link KnownNetwork} object of the connection.
+ *
+ * @return Returns a KnownNetwork object.
+ */
+ @NonNull
+ public KnownNetwork getKnownNetwork() {
+ return mKnownNetwork;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof KnownNetworkConnectionStatus)) return false;
+ KnownNetworkConnectionStatus other = (KnownNetworkConnectionStatus) obj;
+ return mStatus == other.getStatus()
+ && Objects.equals(mKnownNetwork, other.getKnownNetwork());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus, mKnownNetwork);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ mKnownNetwork.writeToParcel(dest, flags);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * Creates a {@link KnownNetworkConnectionStatus} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static KnownNetworkConnectionStatus readFromParcel(@NonNull Parcel in) {
+ return new KnownNetworkConnectionStatus(in.readInt(),
+ KnownNetwork.readFromParcel(in),
+ in.readBundle());
+ }
+
+ @NonNull
+ public static final Creator<KnownNetworkConnectionStatus> CREATOR = new Creator<>() {
+ @Override
+ public KnownNetworkConnectionStatus createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ public KnownNetworkConnectionStatus[] newArray(int size) {
+ return new KnownNetworkConnectionStatus[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("KnownNetworkConnectionStatus[")
+ .append("status=").append(mStatus)
+ .append("known network=").append(mKnownNetwork.toString())
+ .append("extras=").append(mExtras.toString())
+ .append("]").toString();
+ }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.aidl
new file mode 100644
index 000000000000..f3cbbc2963a3
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+parcelable NetworkProviderInfo; \ No newline at end of file
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
new file mode 100644
index 000000000000..e207b01a364d
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A data class representing a device providing connectivity.
+ * This class is used in IPC calls between the implementer of {@link SharedConnectivityService} and
+ * the consumers of {@link com.android.wifitrackerlib}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NetworkProviderInfo implements Parcelable {
+
+ /**
+ * Device type providing connectivity is unknown.
+ */
+ public static final int DEVICE_TYPE_UNKNOWN = 0;
+
+ /**
+ * Device providing connectivity is a mobile phone.
+ */
+ public static final int DEVICE_TYPE_PHONE = 1;
+
+ /**
+ * Device providing connectivity is a tablet.
+ */
+ public static final int DEVICE_TYPE_TABLET = 2;
+
+ /**
+ * Device providing connectivity is a laptop.
+ */
+ public static final int DEVICE_TYPE_LAPTOP = 3;
+
+ /**
+ * Device providing connectivity is a watch.
+ */
+ public static final int DEVICE_TYPE_WATCH = 4;
+
+ /**
+ * Device providing connectivity is a watch.
+ */
+ public static final int DEVICE_TYPE_AUTO = 5;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DEVICE_TYPE_UNKNOWN,
+ DEVICE_TYPE_PHONE,
+ DEVICE_TYPE_TABLET,
+ DEVICE_TYPE_LAPTOP,
+ DEVICE_TYPE_WATCH,
+ DEVICE_TYPE_AUTO
+ })
+ public @interface DeviceType {
+ }
+
+ /**
+ * Key in extras bundle indicating that the device battery is charging.
+ * @hide
+ */
+ public static final String EXTRA_KEY_IS_BATTERY_CHARGING = "is_battery_charging";
+
+ @DeviceType
+ private final int mDeviceType;
+ private final String mDeviceName;
+ private final String mModelName;
+ private final int mBatteryPercentage;
+ private final int mConnectionStrength;
+ private final Bundle mExtras;
+
+ /**
+ * Builder class for {@link NetworkProviderInfo}.
+ */
+ public static final class Builder {
+ private int mDeviceType;
+ private String mDeviceName;
+ private String mModelName;
+ private int mBatteryPercentage;
+ private int mConnectionStrength;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder(@NonNull String deviceName, @NonNull String modelName) {
+ Objects.requireNonNull(deviceName);
+ Objects.requireNonNull(modelName);
+ mDeviceName = deviceName;
+ mModelName = modelName;
+ }
+
+ /**
+ * Sets the device type that provides connectivity.
+ *
+ * @param deviceType Device type as represented by IntDef {@link DeviceType}.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setDeviceType(@DeviceType int deviceType) {
+ mDeviceType = deviceType;
+ return this;
+ }
+
+ /**
+ * Sets the device name of the remote device.
+ *
+ * @param deviceName The user configurable device name.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setDeviceName(@NonNull String deviceName) {
+ Objects.requireNonNull(deviceName);
+ mDeviceName = deviceName;
+ return this;
+ }
+
+ /**
+ * Sets the model name of the remote device.
+ *
+ * @param modelName The OEM configured name for the device model.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setModelName(@NonNull String modelName) {
+ Objects.requireNonNull(modelName);
+ mModelName = modelName;
+ return this;
+ }
+
+ /**
+ * Sets the battery charge percentage of the remote device.
+ *
+ * @param batteryPercentage The battery charge percentage in the range 0 to 100.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setBatteryPercentage(@IntRange(from = 0, to = 100) int batteryPercentage) {
+ mBatteryPercentage = batteryPercentage;
+ return this;
+ }
+
+ /**
+ * Sets the displayed connection strength of the remote device to the internet.
+ *
+ * @param connectionStrength Connection strength in range 0 to 4.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) {
+ mConnectionStrength = connectionStrength;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ Objects.requireNonNull(extras);
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link NetworkProviderInfo} object.
+ *
+ * @return Returns the built {@link NetworkProviderInfo} object.
+ */
+ @NonNull
+ public NetworkProviderInfo build() {
+ return new NetworkProviderInfo(mDeviceType, mDeviceName, mModelName, mBatteryPercentage,
+ mConnectionStrength, mExtras);
+ }
+ }
+
+ private static void validate(@DeviceType int deviceType, String deviceName, String modelName,
+ int batteryPercentage, int connectionStrength) {
+ if (deviceType != DEVICE_TYPE_UNKNOWN && deviceType != DEVICE_TYPE_PHONE
+ && deviceType != DEVICE_TYPE_TABLET && deviceType != DEVICE_TYPE_LAPTOP
+ && deviceType != DEVICE_TYPE_WATCH && deviceType != DEVICE_TYPE_AUTO) {
+ throw new IllegalArgumentException("Illegal device type");
+ }
+ if (batteryPercentage < 0 || batteryPercentage > 100) {
+ throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
+ }
+ if (connectionStrength < 0 || connectionStrength > 4) {
+ throw new IllegalArgumentException("ConnectionStrength must be in range 0-4");
+ }
+ }
+
+ private NetworkProviderInfo(@DeviceType int deviceType, @NonNull String deviceName,
+ @NonNull String modelName, int batteryPercentage, int connectionStrength,
+ @NonNull Bundle extras) {
+ validate(deviceType, deviceName, modelName, batteryPercentage, connectionStrength);
+ mDeviceType = deviceType;
+ mDeviceName = deviceName;
+ mModelName = modelName;
+ mBatteryPercentage = batteryPercentage;
+ mConnectionStrength = connectionStrength;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the device type that provides connectivity.
+ *
+ * @return Returns the device type as represented by IntDef {@link DeviceType}.
+ */
+ @DeviceType
+ public int getDeviceType() {
+ return mDeviceType;
+ }
+
+ /**
+ * Gets the device name of the remote device.
+ *
+ * @return Returns the user configurable device name.
+ */
+ @NonNull
+ public String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Gets the model name of the remote device.
+ *
+ * @return Returns the OEM configured name for the device model.
+ */
+ @NonNull
+ public String getModelName() {
+ return mModelName;
+ }
+
+ /**
+ * Gets the battery charge percentage of the remote device.
+ *
+ * @return Returns the battery charge percentage in the range 0 to 100.
+ */
+ @IntRange(from = 0, to = 100)
+ public int getBatteryPercentage() {
+ return mBatteryPercentage;
+ }
+
+ /**
+ * Gets the displayed connection strength of the remote device to the internet.
+ *
+ * @return Returns the connection strength in range 0 to 4.
+ */
+ @IntRange(from = 0, to = 4)
+ public int getConnectionStrength() {
+ return mConnectionStrength;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof NetworkProviderInfo)) return false;
+ NetworkProviderInfo other = (NetworkProviderInfo) obj;
+ return mDeviceType == other.getDeviceType()
+ && Objects.equals(mDeviceName, other.mDeviceName)
+ && Objects.equals(mModelName, other.mModelName)
+ && mBatteryPercentage == other.mBatteryPercentage
+ && mConnectionStrength == other.mConnectionStrength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDeviceType, mDeviceName, mModelName, mBatteryPercentage,
+ mConnectionStrength);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mDeviceType);
+ dest.writeString(mDeviceName);
+ dest.writeString(mModelName);
+ dest.writeInt(mBatteryPercentage);
+ dest.writeInt(mConnectionStrength);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Creates a {@link NetworkProviderInfo} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static NetworkProviderInfo readFromParcel(@NonNull Parcel in) {
+ return new NetworkProviderInfo(in.readInt(), in.readString(), in.readString(), in.readInt(),
+ in.readInt(), in.readBundle());
+ }
+
+ @NonNull
+ public static final Creator<NetworkProviderInfo> CREATOR = new Creator<NetworkProviderInfo>() {
+ @Override
+ public NetworkProviderInfo createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ public NetworkProviderInfo[] newArray(int size) {
+ return new NetworkProviderInfo[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("NetworkProviderInfo[")
+ .append("deviceType=").append(mDeviceType)
+ .append(", deviceName=").append(mDeviceName)
+ .append(", modelName=").append(mModelName)
+ .append(", batteryPercentage=").append(mBatteryPercentage)
+ .append(", connectionStrength=").append(mConnectionStrength)
+ .append(", extras=").append(mExtras.toString())
+ .append("]").toString();
+ }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java
new file mode 100644
index 000000000000..eb04df64d6d0
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityClientCallback.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+
+import java.util.List;
+
+/**
+ * Interface for clients of {@link SharedConnectivityManager} to register for changes in network
+ * status.
+ *
+ * @hide
+ */
+@SystemApi
+public interface SharedConnectivityClientCallback {
+ /**
+ * This method is being called by {@link SharedConnectivityService} to notify of a change in the
+ * list of available Hotspot Networks.
+ *
+ * @param networks Updated Hotspot Network list.
+ */
+ void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks);
+
+ /**
+ * This method is being called by {@link SharedConnectivityService} to notify of a change in the
+ * list of available Known Networks.
+ *
+ * @param networks Updated Known Network list.
+ */
+ void onKnownNetworksUpdated(@NonNull List<KnownNetwork> networks);
+
+ /**
+ * This method is being called by {@link SharedConnectivityService} to notify of a change in the
+ * state of share connectivity settings.
+ *
+ * @param state The new state.
+ */
+ void onSharedConnectivitySettingsChanged(@NonNull SharedConnectivitySettingsState state);
+
+ /**
+ * This method is being called by {@link SharedConnectivityService} to notify of a change in the
+ * status of the current hotspot network connection.
+ *
+ * @param status The new status.
+ */
+ void onHotspotNetworkConnectionStatusChanged(@NonNull HotspotNetworkConnectionStatus status);
+
+ /**
+ * This method is being called by {@link SharedConnectivityService} to notify of a change in the
+ * status of the current known network connection.
+ *
+ * @param status The new status.
+ */
+ void onKnownNetworkConnectionStatusChanged(@NonNull KnownNetworkConnectionStatus status);
+
+ /**
+ * This method is being called when the service is ready to be used.
+ */
+ void onServiceConnected();
+
+ /**
+ * This method is being called when the service is no longer available.
+ */
+ void onServiceDisconnected();
+
+ /**
+ * This method is called when the registration of the callback with the shared connectivity
+ * service failed.
+ *
+ * @param exception The exception received from the system when trying to connect to the
+ * service.
+ */
+ void onRegisterCallbackFailed(@NonNull Exception exception);
+}
+
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
new file mode 100644
index 000000000000..feef0497c152
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.res.Resources;
+import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback;
+import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService;
+import android.net.wifi.sharedconnectivity.service.SharedConnectivityService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * This class is the library used by consumers of Shared Connectivity data to bind to the service,
+ * receive callbacks from, and send user actions to the service.
+ *
+ * A client must register at least one callback so that the manager will bind to the service. Once
+ * all callbacks are unregistered, the manager will unbind from the service. When the client no
+ * longer needs Shared Connectivity data, the client must unregister.
+ *
+ * The methods {@link #connectHotspotNetwork}, {@link #disconnectHotspotNetwork},
+ * {@link #connectKnownNetwork} and {@link #forgetKnownNetwork} are not valid and will return false
+ * and getter methods will fail and return null if not called between
+ * {@link SharedConnectivityClientCallback#onServiceConnected()}
+ * and {@link SharedConnectivityClientCallback#onServiceDisconnected()} or if
+ * {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} was called.
+ *
+ * @hide
+ */
+@SystemApi
+public class SharedConnectivityManager {
+ private static final String TAG = SharedConnectivityManager.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ private static final class SharedConnectivityCallbackProxy extends
+ ISharedConnectivityCallback.Stub {
+ private final Executor mExecutor;
+ private final SharedConnectivityClientCallback mCallback;
+
+ SharedConnectivityCallbackProxy(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SharedConnectivityClientCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onHotspotNetworksUpdated(networks));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public void onKnownNetworksUpdated(@NonNull List<KnownNetwork> networks) {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onKnownNetworksUpdated(networks));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public void onSharedConnectivitySettingsChanged(
+ @NonNull SharedConnectivitySettingsState state) {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onSharedConnectivitySettingsChanged(state));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ public void onHotspotNetworkConnectionStatusChanged(
+ @NonNull HotspotNetworkConnectionStatus status) {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() ->
+ mCallback.onHotspotNetworkConnectionStatusChanged(status));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
+ public void onKnownNetworkConnectionStatusChanged(
+ @NonNull KnownNetworkConnectionStatus status) {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() ->
+ mCallback.onKnownNetworkConnectionStatusChanged(status));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+
+ private ISharedConnectivityService mService;
+ @GuardedBy("mProxyDataLock")
+ private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy>
+ mProxyMap = new HashMap<>();
+ @GuardedBy("mProxyDataLock")
+ private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy>
+ mCallbackProxyCache = new HashMap<>();
+ // Makes sure mProxyMap and mCallbackProxyCache are locked together when one of them is used.
+ private final Object mProxyDataLock = new Object();
+ private final Context mContext;
+ private final String mServicePackageName;
+ private final String mIntentAction;
+ private ServiceConnection mServiceConnection;
+
+ /**
+ * Creates a new instance of {@link SharedConnectivityManager}.
+ *
+ * @return An instance of {@link SharedConnectivityManager} or null if the shared connectivity
+ * service is not found.
+ * @hide
+ */
+ @Nullable
+ public static SharedConnectivityManager create(@NonNull Context context) {
+ Resources resources = context.getResources();
+ try {
+ String servicePackageName = resources.getString(
+ R.string.config_sharedConnectivityServicePackage);
+ String serviceIntentAction = resources.getString(
+ R.string.config_sharedConnectivityServiceIntentAction);
+ if (TextUtils.isEmpty(servicePackageName) || TextUtils.isEmpty(serviceIntentAction)) {
+ Log.e(TAG, "To support shared connectivity service on this device, the"
+ + " service's package name and intent action strings must not be empty");
+ return null;
+ }
+ return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction);
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "To support shared connectivity service on this device, the service's"
+ + " package name and intent action strings must be defined");
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ @SuppressLint("ManagerLookup")
+ @TestApi
+ @Nullable
+ public static SharedConnectivityManager create(@NonNull Context context,
+ @NonNull String servicePackageName, @NonNull String serviceIntentAction) {
+ return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction);
+ }
+
+ private SharedConnectivityManager(@NonNull Context context, String servicePackageName,
+ String serviceIntentAction) {
+ mContext = context;
+ mServicePackageName = servicePackageName;
+ mIntentAction = serviceIntentAction;
+ }
+
+ private void bind() {
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = ISharedConnectivityService.Stub.asInterface(service);
+ synchronized (mProxyDataLock) {
+ if (!mCallbackProxyCache.isEmpty()) {
+ mCallbackProxyCache.keySet().forEach(callback ->
+ registerCallbackInternal(
+ callback, mCallbackProxyCache.get(callback)));
+ mCallbackProxyCache.clear();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Log.i(TAG, "onServiceDisconnected");
+ mService = null;
+ synchronized (mProxyDataLock) {
+ if (!mCallbackProxyCache.isEmpty()) {
+ mCallbackProxyCache.keySet().forEach(
+ SharedConnectivityClientCallback::onServiceDisconnected);
+ mCallbackProxyCache.clear();
+ }
+ if (!mProxyMap.isEmpty()) {
+ mProxyMap.keySet().forEach(
+ SharedConnectivityClientCallback::onServiceDisconnected);
+ mProxyMap.clear();
+ }
+ }
+ }
+ };
+
+ mContext.bindService(
+ new Intent().setPackage(mServicePackageName).setAction(mIntentAction),
+ mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private void registerCallbackInternal(SharedConnectivityClientCallback callback,
+ SharedConnectivityCallbackProxy proxy) {
+ try {
+ mService.registerCallback(proxy);
+ synchronized (mProxyDataLock) {
+ mProxyMap.put(callback, proxy);
+ }
+ callback.onServiceConnected();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in registerCallback", e);
+ callback.onRegisterCallbackFailed(e);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public void setService(@Nullable IInterface service) {
+ mService = (ISharedConnectivityService) service;
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public ServiceConnection getServiceConnection() {
+ return mServiceConnection;
+ }
+
+ private void unbind() {
+ if (mServiceConnection != null) {
+ mContext.unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
+ }
+
+ /**
+ * Registers a callback for receiving updates to the list of Hotspot Networks, Known Networks,
+ * shared connectivity settings state, hotspot network connection status and known network
+ * connection status.
+ * Automatically binds to implementation of {@link SharedConnectivityService} specified in
+ * the device overlay when the first callback is registered.
+ * The {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} will be called if the
+ * registration failed.
+ *
+ * @param executor The Executor used to invoke the callback.
+ * @param callback The callback of type {@link SharedConnectivityClientCallback} that is invoked
+ * when the service updates its data.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public void registerCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull SharedConnectivityClientCallback callback) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ if (mProxyMap.containsKey(callback) || mCallbackProxyCache.containsKey(callback)) {
+ Log.e(TAG, "Callback already registered");
+ callback.onRegisterCallbackFailed(new IllegalStateException(
+ "Callback already registered"));
+ return;
+ }
+
+ SharedConnectivityCallbackProxy proxy =
+ new SharedConnectivityCallbackProxy(executor, callback);
+ if (mService == null) {
+ boolean shouldBind;
+ synchronized (mProxyDataLock) {
+ // Size can be 1 in different cases of register/unregister sequences. If size is 0
+ // Bind never happened or unbind was called.
+ shouldBind = mCallbackProxyCache.size() == 0;
+ mCallbackProxyCache.put(callback, proxy);
+ }
+ if (shouldBind) {
+ bind();
+ }
+ return;
+ }
+ registerCallbackInternal(callback, proxy);
+ }
+
+ /**
+ * Unregisters a callback.
+ * Unbinds from {@link SharedConnectivityService} when no more callbacks are registered.
+ *
+ * @return Returns true if the callback was successfully unregistered, false otherwise.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public boolean unregisterCallback(
+ @NonNull SharedConnectivityClientCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ if (!mProxyMap.containsKey(callback) && !mCallbackProxyCache.containsKey(callback)) {
+ Log.e(TAG, "Callback not found, cannot unregister");
+ return false;
+ }
+
+ if (mService == null) {
+ boolean shouldUnbind;
+ synchronized (mProxyDataLock) {
+ mCallbackProxyCache.remove(callback);
+ // Connection was never established, so all registered callbacks are in the cache.
+ shouldUnbind = mCallbackProxyCache.isEmpty();
+ }
+ if (shouldUnbind) {
+ unbind();
+ }
+ return true;
+ }
+
+ try {
+ boolean shouldUnbind;
+ synchronized (mProxyDataLock) {
+ mService.unregisterCallback(mProxyMap.get(callback));
+ mProxyMap.remove(callback);
+ shouldUnbind = mProxyMap.isEmpty();
+ }
+ if (shouldUnbind) {
+ unbind();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in unregisterCallback", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Send command to the implementation of {@link SharedConnectivityService} requesting connection
+ * to the specified Hotspot Network.
+ *
+ * @param network {@link HotspotNetwork} object representing the network the user has requested
+ * a connection to.
+ * @return Returns true if the service received the command. Does not guarantee that the
+ * connection was successful.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public boolean connectHotspotNetwork(@NonNull HotspotNetwork network) {
+ Objects.requireNonNull(network, "Hotspot network cannot be null");
+
+ if (mService == null) {
+ return false;
+ }
+
+ try {
+ mService.connectHotspotNetwork(network);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in connectHotspotNetwork", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Send command to the implementation of {@link SharedConnectivityService} requesting
+ * disconnection from the active Hotspot Network.
+ *
+ * @param network {@link HotspotNetwork} object representing the network the user has requested
+ * to disconnect from.
+ * @return Returns true if the service received the command. Does not guarantee that the
+ * disconnection was successful.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public boolean disconnectHotspotNetwork(@NonNull HotspotNetwork network) {
+ if (mService == null) {
+ return false;
+ }
+
+ try {
+ mService.disconnectHotspotNetwork(network);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in disconnectHotspotNetwork", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Send command to the implementation of {@link SharedConnectivityService} requesting connection
+ * to the specified Known Network.
+ *
+ * @param network {@link KnownNetwork} object representing the network the user has requested
+ * a connection to.
+ * @return Returns true if the service received the command. Does not guarantee that the
+ * connection was successful.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public boolean connectKnownNetwork(@NonNull KnownNetwork network) {
+ Objects.requireNonNull(network, "Known network cannot be null");
+
+ if (mService == null) {
+ return false;
+ }
+
+ try {
+ mService.connectKnownNetwork(network);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in connectKnownNetwork", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Send command to the implementation of {@link SharedConnectivityService} requesting removal of
+ * the specified Known Network from the list of Known Networks.
+ *
+ * @return Returns true if the service received the command. Does not guarantee that the
+ * forget action was successful.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public boolean forgetKnownNetwork(@NonNull KnownNetwork network) {
+ Objects.requireNonNull(network, "Known network cannot be null");
+
+ if (mService == null) {
+ return false;
+ }
+
+ try {
+ mService.forgetKnownNetwork(network);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in forgetKnownNetwork", e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Gets the list of hotspot networks the user can select to connect to.
+ *
+ * @return Returns a {@link List} of {@link HotspotNetwork} objects, null on failure.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @SuppressWarnings("NullableCollection")
+ @Nullable
+ public List<HotspotNetwork> getHotspotNetworks() {
+ if (mService == null) {
+ return null;
+ }
+
+ try {
+ return mService.getHotspotNetworks();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in getHotspotNetworks", e);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the list of known networks the user can select to connect to.
+ *
+ * @return Returns a {@link List} of {@link KnownNetwork} objects, null on failure.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @SuppressWarnings("NullableCollection")
+ @Nullable
+ public List<KnownNetwork> getKnownNetworks() {
+ if (mService == null) {
+ return null;
+ }
+
+ try {
+ return mService.getKnownNetworks();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in getKnownNetworks", e);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the shared connectivity settings state.
+ *
+ * @return Returns a {@link SharedConnectivitySettingsState} object with the state, null on
+ * failure.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Nullable
+ public SharedConnectivitySettingsState getSettingsState() {
+ if (mService == null) {
+ return null;
+ }
+
+ try {
+ return mService.getSettingsState();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in getSettingsState", e);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the connection status of the hotspot network the user selected to connect to.
+ *
+ * @return Returns a {@link HotspotNetworkConnectionStatus} object with the connection status,
+ * null on failure. If no connection is active the status will be
+ * {@link HotspotNetworkConnectionStatus#CONNECTION_STATUS_UNKNOWN}.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Nullable
+ public HotspotNetworkConnectionStatus getHotspotNetworkConnectionStatus() {
+ if (mService == null) {
+ return null;
+ }
+
+ try {
+ return mService.getHotspotNetworkConnectionStatus();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in getHotspotNetworkConnectionStatus", e);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the connection status of the known network the user selected to connect to.
+ *
+ * @return Returns a {@link KnownNetworkConnectionStatus} object with the connection status,
+ * null on failure. If no connection is active the status will be
+ * {@link KnownNetworkConnectionStatus#CONNECTION_STATUS_UNKNOWN}.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Nullable
+ public KnownNetworkConnectionStatus getKnownNetworkConnectionStatus() {
+ if (mService == null) {
+ return null;
+ }
+
+ try {
+ return mService.getKnownNetworkConnectionStatus();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in getKnownNetworkConnectionStatus", e);
+ }
+ return null;
+ }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl
new file mode 100644
index 000000000000..289afacb9aa0
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+parcelable SharedConnectivitySettingsState; \ No newline at end of file
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
new file mode 100644
index 000000000000..5ad3ede8498d
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+
+/**
+ * A data class representing the shared connectivity settings state.
+ *
+ * This class represents a snapshot of the settings and can be out of date if the settings changed
+ * after receiving an object of this class.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SharedConnectivitySettingsState implements Parcelable {
+
+ private final boolean mInstantTetherEnabled;
+ private final PendingIntent mInstantTetherSettingsPendingIntent;
+ private final Bundle mExtras;
+
+ /**
+ * Builder class for {@link SharedConnectivitySettingsState}.
+ */
+ public static final class Builder {
+ private boolean mInstantTetherEnabled;
+ private PendingIntent mInstantTetherSettingsPendingIntent;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the state of Instant Tether in settings
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setInstantTetherEnabled(boolean instantTetherEnabled) {
+ mInstantTetherEnabled = instantTetherEnabled;
+ return this;
+ }
+
+ /**
+ * Sets the {@link PendingIntent} that will open the Instant Tether settings page.
+ * The pending intent must be set as {@link PendingIntent#FLAG_IMMUTABLE}.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setInstantTetherSettingsPendingIntent(@NonNull PendingIntent pendingIntent) {
+ mInstantTetherSettingsPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link SharedConnectivitySettingsState} object.
+ *
+ * @return Returns the built {@link SharedConnectivitySettingsState} object.
+ */
+ @NonNull
+ public SharedConnectivitySettingsState build() {
+ return new SharedConnectivitySettingsState(mInstantTetherEnabled,
+ mInstantTetherSettingsPendingIntent, mExtras);
+
+ }
+ }
+
+ private static void validate(PendingIntent pendingIntent) {
+ if (pendingIntent != null && !pendingIntent.isImmutable()) {
+ throw new IllegalArgumentException("Pending intent must be immutable");
+ }
+ }
+
+ private SharedConnectivitySettingsState(boolean instantTetherEnabled,
+ PendingIntent pendingIntent, @NonNull Bundle extras) {
+ validate(pendingIntent);
+ mInstantTetherEnabled = instantTetherEnabled;
+ mInstantTetherSettingsPendingIntent = pendingIntent;
+ mExtras = extras;
+ }
+
+ /**
+ * Gets the state of Instant Tether in settings
+ *
+ * @return Returns true for enabled, false otherwise.
+ */
+ public boolean isInstantTetherEnabled() {
+ return mInstantTetherEnabled;
+ }
+
+ /**
+ * Gets the pending intent to open Instant Tether settings page.
+ *
+ * @return Returns the pending intent that opens the settings page, null if none.
+ */
+ @Nullable
+ public PendingIntent getInstantTetherSettingsPendingIntent() {
+ return mInstantTetherSettingsPendingIntent;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SharedConnectivitySettingsState)) return false;
+ SharedConnectivitySettingsState other = (SharedConnectivitySettingsState) obj;
+ return mInstantTetherEnabled == other.isInstantTetherEnabled()
+ && Objects.equals(mInstantTetherSettingsPendingIntent,
+ other.getInstantTetherSettingsPendingIntent());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mInstantTetherEnabled, mInstantTetherSettingsPendingIntent);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ PendingIntent.writePendingIntentOrNullToParcel(mInstantTetherSettingsPendingIntent, dest);
+ dest.writeBoolean(mInstantTetherEnabled);
+ dest.writeBundle(mExtras);
+ }
+
+ /**
+ * Creates a {@link SharedConnectivitySettingsState} object from a parcel.
+ *
+ * @hide
+ */
+ @NonNull
+ public static SharedConnectivitySettingsState readFromParcel(@NonNull Parcel in) {
+ PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(in);
+ boolean instantTetherEnabled = in.readBoolean();
+ Bundle extras = in.readBundle();
+ return new SharedConnectivitySettingsState(instantTetherEnabled, pendingIntent, extras);
+ }
+
+ @NonNull
+ public static final Creator<SharedConnectivitySettingsState> CREATOR = new Creator<>() {
+ @Override
+ public SharedConnectivitySettingsState createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ public SharedConnectivitySettingsState[] newArray(int size) {
+ return new SharedConnectivitySettingsState[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("SharedConnectivitySettingsState[")
+ .append("instantTetherEnabled=").append(mInstantTetherEnabled)
+ .append("PendingIntent=").append(mInstantTetherSettingsPendingIntent.toString())
+ .append("extras=").append(mExtras.toString())
+ .append("]").toString();
+ }
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
new file mode 100644
index 000000000000..737aa6d9964c
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.service;
+
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
+import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
+
+/*
+ * @hide
+ */
+interface ISharedConnectivityCallback {
+ oneway void onHotspotNetworksUpdated(in List<HotspotNetwork> networks);
+ oneway void onHotspotNetworkConnectionStatusChanged(in HotspotNetworkConnectionStatus status);
+ oneway void onKnownNetworksUpdated(in List<KnownNetwork> networks);
+ oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status);
+ oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl
new file mode 100644
index 000000000000..c81380df3c79
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityService.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.service;
+
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
+import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback;
+
+/*
+ * @hide
+ */
+interface ISharedConnectivityService {
+ void registerCallback(in ISharedConnectivityCallback callback);
+ void unregisterCallback(in ISharedConnectivityCallback callback);
+ void connectHotspotNetwork(in HotspotNetwork network);
+ void disconnectHotspotNetwork(in HotspotNetwork network);
+ void connectKnownNetwork(in KnownNetwork network);
+ void forgetKnownNetwork(in KnownNetwork network);
+ List<HotspotNetwork> getHotspotNetworks();
+ List<KnownNetwork> getKnownNetworks();
+ SharedConnectivitySettingsState getSettingsState();
+ HotspotNetworkConnectionStatus getHotspotNetworkConnectionStatus();
+ KnownNetworkConnectionStatus getKnownNetworkConnectionStatus();
+}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
new file mode 100644
index 000000000000..2bbe91958383
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.service;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_SETUP_WIZARD;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
+import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+
+
+/**
+ * This class is the partly implemented service for injecting Shared Connectivity networks into the
+ * Wi-Fi Pickers and other relevant UI surfaces.
+ *
+ * Implementing application should extend this service and override the indicated methods.
+ * Callers to the service should use {@link SharedConnectivityManager} to bind to the implemented
+ * service as specified in the configuration overlay.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SharedConnectivityService extends Service {
+ private static final String TAG = SharedConnectivityService.class.getSimpleName();
+ private static final boolean DEBUG = true;
+
+ private Handler mHandler;
+ private final RemoteCallbackList<ISharedConnectivityCallback> mRemoteCallbackList =
+ new RemoteCallbackList<>();
+ private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList();
+ private List<KnownNetwork> mKnownNetworks = Collections.emptyList();
+ private SharedConnectivitySettingsState mSettingsState = null;
+ private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus =
+ new HotspotNetworkConnectionStatus.Builder()
+ .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
+ .setExtras(Bundle.EMPTY).build();
+ private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus =
+ new KnownNetworkConnectionStatus.Builder()
+ .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN)
+ .setExtras(Bundle.EMPTY).build();
+ // Used for testing
+ private CountDownLatch mCountDownLatch;
+
+ @Override
+ @Nullable
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (DEBUG) Log.i(TAG, "onBind intent=" + intent);
+ mHandler = new Handler(getMainLooper());
+ IBinder serviceStub = new ISharedConnectivityService.Stub() {
+
+ /**
+ * Registers a callback for receiving updates to the list of Hotspot Networks, Known
+ * Networks, shared connectivity settings state, hotspot network connection status and
+ * known network connection status.
+ *
+ * @param callback The callback of type {@link ISharedConnectivityCallback} to be called
+ * when there is update to the data.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public void registerCallback(ISharedConnectivityCallback callback) {
+ checkPermissions();
+ mHandler.post(() -> onRegisterCallback(callback));
+ }
+
+ /**
+ * Unregisters a previously registered callback.
+ *
+ * @param callback The callback to unregister.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public void unregisterCallback(ISharedConnectivityCallback callback) {
+ checkPermissions();
+ mHandler.post(() -> onUnregisterCallback(callback));
+ }
+
+ /**
+ * Connects to a hotspot network.
+ *
+ * @param network The network to connect to.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public void connectHotspotNetwork(HotspotNetwork network) {
+ checkPermissions();
+ mHandler.post(() -> onConnectHotspotNetwork(network));
+ }
+
+ /**
+ * Disconnects from a previously connected hotspot network.
+ *
+ * @param network The network to disconnect from.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ public void disconnectHotspotNetwork(HotspotNetwork network) {
+ checkPermissions();
+ mHandler.post(() -> onDisconnectHotspotNetwork(network));
+ }
+
+ /**
+ * Adds a known network to the available networks on the device and connects to it.
+ *
+ * @param network The network to connect to.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public void connectKnownNetwork(KnownNetwork network) {
+ checkPermissions();
+ mHandler.post(() -> onConnectKnownNetwork(network));
+ }
+
+ /**
+ * Removes a known network from the available networks on the device which will also
+ * disconnect the device from the network if it is connected to it.
+ *
+ * @param network The network to forget.
+ */
+ @Override
+ public void forgetKnownNetwork(KnownNetwork network) {
+ checkPermissions();
+ mHandler.post(() -> onForgetKnownNetwork(network));
+ }
+
+ /**
+ * Gets the list of hotspot networks the user can select to connect to.
+ *
+ * @return Returns a {@link List} of {@link HotspotNetwork} objects
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public List<HotspotNetwork> getHotspotNetworks() {
+ checkPermissions();
+ return mHotspotNetworks;
+ }
+
+ /**
+ * Gets the list of known networks the user can select to connect to.
+ *
+ * @return Returns a {@link List} of {@link KnownNetwork} objects.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public List<KnownNetwork> getKnownNetworks() {
+ checkPermissions();
+ return mKnownNetworks;
+ }
+
+ /**
+ * Gets the shared connectivity settings state.
+ *
+ * @return Returns a {@link SharedConnectivitySettingsState} object with the state.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public SharedConnectivitySettingsState getSettingsState() {
+ checkPermissions();
+ // Done lazily since creating it needs a context.
+ if (mSettingsState == null) {
+ mSettingsState = new SharedConnectivitySettingsState
+ .Builder()
+ .setInstantTetherEnabled(false)
+ .setExtras(Bundle.EMPTY).build();
+ }
+ return mSettingsState;
+ }
+
+ /**
+ * Gets the connection status of the hotspot network the user selected to connect to.
+ *
+ * @return Returns a {@link HotspotNetworkConnectionStatus} object with the connection
+ * status.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public HotspotNetworkConnectionStatus getHotspotNetworkConnectionStatus() {
+ checkPermissions();
+ return mHotspotNetworkConnectionStatus;
+ }
+
+ /**
+ * Gets the connection status of the known network the user selected to connect to.
+ *
+ * @return Returns a {@link KnownNetworkConnectionStatus} object with the connection
+ * status.
+ */
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ @Override
+ public KnownNetworkConnectionStatus getKnownNetworkConnectionStatus() {
+ checkPermissions();
+ return mKnownNetworkConnectionStatus;
+ }
+
+ @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD})
+ /**
+ * checkPermissions is using checkCallingOrSelfPermission to support CTS testing of this
+ * service. This does allow a process to bind to itself if it holds the proper
+ * permission. We do not consider this to be an issue given that the process can already
+ * access the service data since they are in the same process.
+ */
+ private void checkPermissions() {
+ if (checkCallingOrSelfPermission(NETWORK_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED
+ && checkCallingOrSelfPermission(NETWORK_SETUP_WIZARD)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Calling process must have NETWORK_SETTINGS or"
+ + " NETWORK_SETUP_WIZARD permission");
+ }
+ }
+ };
+ onBind(); // For CTS testing
+ return serviceStub;
+ }
+
+ /** @hide */
+ @TestApi
+ public void onBind() {
+ }
+
+ /** @hide */
+ @TestApi
+ public final void setCountdownLatch(@Nullable CountDownLatch latch) {
+ mCountDownLatch = latch;
+ }
+
+ private void onRegisterCallback(ISharedConnectivityCallback callback) {
+ mRemoteCallbackList.register(callback);
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ private void onUnregisterCallback(ISharedConnectivityCallback callback) {
+ mRemoteCallbackList.unregister(callback);
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ /**
+ * Implementing application should call this method to provide an up-to-date list of Hotspot
+ * Networks to be displayed to the user.
+ *
+ * This method updates the cached list and notifies all registered callbacks. Any callbacks that
+ * are inaccessible will be unregistered.
+ *
+ * @param networks The updated list of {@link HotspotNetwork} objects.
+ */
+ public final void setHotspotNetworks(@NonNull List<HotspotNetwork> networks) {
+ mHotspotNetworks = networks;
+
+ int count = mRemoteCallbackList.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ try {
+ mRemoteCallbackList.getBroadcastItem(i).onHotspotNetworksUpdated(mHotspotNetworks);
+ } catch (RemoteException e) {
+ if (DEBUG) Log.w(TAG, "Exception in setHotspotNetworks", e);
+ }
+ }
+ mRemoteCallbackList.finishBroadcast();
+ }
+
+ /**
+ * Implementing application should call this method to provide an up-to-date list of Known
+ * Networks to be displayed to the user.
+ *
+ * This method updates the cached list and notifies all registered callbacks. Any callbacks that
+ * are inaccessible will be unregistered.
+ *
+ * @param networks The updated list of {@link KnownNetwork} objects.
+ */
+ public final void setKnownNetworks(@NonNull List<KnownNetwork> networks) {
+ mKnownNetworks = networks;
+
+ int count = mRemoteCallbackList.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ try {
+ mRemoteCallbackList.getBroadcastItem(i).onKnownNetworksUpdated(mKnownNetworks);
+ } catch (RemoteException e) {
+ if (DEBUG) Log.w(TAG, "Exception in setKnownNetworks", e);
+ }
+ }
+ mRemoteCallbackList.finishBroadcast();
+ }
+
+ /**
+ * Implementing application should call this method to provide an up-to-date state of Shared
+ * connectivity settings state.
+ *
+ * This method updates the cached state and notifies all registered callbacks. Any callbacks
+ * that are inaccessible will be unregistered.
+ *
+ * @param settingsState The updated state {@link SharedConnectivitySettingsState}
+ * objects.
+ */
+ public final void setSettingsState(@NonNull SharedConnectivitySettingsState settingsState) {
+ mSettingsState = settingsState;
+
+ int count = mRemoteCallbackList.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ try {
+ mRemoteCallbackList.getBroadcastItem(i).onSharedConnectivitySettingsChanged(
+ mSettingsState);
+ } catch (RemoteException e) {
+ if (DEBUG) Log.w(TAG, "Exception in setSettingsState", e);
+ }
+ }
+ mRemoteCallbackList.finishBroadcast();
+ }
+
+ /**
+ * Implementing application should call this method to provide an up-to-date status of enabling
+ * and connecting to the hotspot network.
+ *
+ * @param status The updated status {@link HotspotNetworkConnectionStatus} of the connection.
+ */
+ public final void updateHotspotNetworkConnectionStatus(
+ @NonNull HotspotNetworkConnectionStatus status) {
+ mHotspotNetworkConnectionStatus = status;
+
+ int count = mRemoteCallbackList.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ try {
+ mRemoteCallbackList
+ .getBroadcastItem(i).onHotspotNetworkConnectionStatusChanged(
+ mHotspotNetworkConnectionStatus);
+ } catch (RemoteException e) {
+ if (DEBUG) Log.w(TAG, "Exception in updateHotspotNetworkConnectionStatus", e);
+ }
+ }
+ mRemoteCallbackList.finishBroadcast();
+ }
+
+ /**
+ * Implementing application should call this method to provide an up-to-date status of
+ * connecting to a known network.
+ *
+ * @param status The updated status {@link KnownNetworkConnectionStatus} of the connection.
+ */
+ public final void updateKnownNetworkConnectionStatus(
+ @NonNull KnownNetworkConnectionStatus status) {
+ mKnownNetworkConnectionStatus = status;
+
+ int count = mRemoteCallbackList.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ try {
+ mRemoteCallbackList
+ .getBroadcastItem(i).onKnownNetworkConnectionStatusChanged(
+ mKnownNetworkConnectionStatus);
+ } catch (RemoteException e) {
+ if (DEBUG) Log.w(TAG, "Exception in updateKnownNetworkConnectionStatus", e);
+ }
+ }
+ mRemoteCallbackList.finishBroadcast();
+ }
+
+ /**
+ * System and settings UI support on the device for instant tether.
+ * @return True if the UI can display Instant Tether network data. False otherwise.
+ */
+ public static boolean areHotspotNetworksEnabledForService(@NonNull Context context) {
+ String servicePackage = context.getResources()
+ .getString(R.string.config_sharedConnectivityServicePackage);
+ return Objects.equals(context.getPackageName(), servicePackage)
+ && context.getResources()
+ .getBoolean(R.bool.config_hotspotNetworksEnabledForService);
+ }
+
+ /**
+ * System and settings UI support on the device for known networks.
+ * @return True if the UI can display known networks data. False otherwise.
+ */
+ public static boolean areKnownNetworksEnabledForService(@NonNull Context context) {
+ String servicePackage = context.getResources()
+ .getString(R.string.config_sharedConnectivityServicePackage);
+ return Objects.equals(context.getPackageName(), servicePackage)
+ && context.getResources()
+ .getBoolean(R.bool.config_knownNetworksEnabledForService);
+ }
+
+ /**
+ * Implementing application should implement this method.
+ *
+ * Implementation should initiate a connection to the Hotspot Network indicated.
+ *
+ * @param network Object identifying the Hotspot Network the user has requested a connection to.
+ */
+ public abstract void onConnectHotspotNetwork(@NonNull HotspotNetwork network);
+
+ /**
+ * Implementing application should implement this method.
+ *
+ * Implementation should initiate a disconnection from the active Hotspot Network.
+ *
+ * @param network Object identifying the Hotspot Network the user has requested to disconnect.
+ */
+ public abstract void onDisconnectHotspotNetwork(@NonNull HotspotNetwork network);
+
+ /**
+ * Implementing application should implement this method.
+ *
+ * Implementation should initiate a connection to the Known Network indicated.
+ *
+ * @param network Object identifying the Known Network the user has requested a connection to.
+ */
+ public abstract void onConnectKnownNetwork(@NonNull KnownNetwork network);
+
+ /**
+ * Implementing application should implement this method.
+ *
+ * Implementation should remove the Known Network indicated from the synced list of networks.
+ *
+ * @param network Object identifying the Known Network the user has requested to forget.
+ */
+ public abstract void onForgetKnownNetwork(@NonNull KnownNetwork network);
+}
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
index c9105f7454e4..7a299694741a 100644
--- a/wifi/tests/Android.bp
+++ b/wifi/tests/Android.bp
@@ -36,6 +36,7 @@ android_test {
static_libs: [
"androidx.test.rules",
+ "androidx.test.core",
"frameworks-base-testutils",
"guava",
"mockito-target-minus-junit4",
diff --git a/wifi/tests/src/android/net/wifi/nl80211/OWNERS b/wifi/tests/src/android/net/wifi/nl80211/OWNERS
new file mode 100644
index 000000000000..8a75e25cb2f6
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/nl80211/OWNERS
@@ -0,0 +1 @@
+kumachang@google.com
diff --git a/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java b/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
index fd595fa5660c..2fa17a199356 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
@@ -47,6 +47,7 @@ public class SingleScanSettingsTest {
private ChannelSettings mChannelSettings2;
private HiddenNetwork mHiddenNetwork1;
private HiddenNetwork mHiddenNetwork2;
+ private byte[] mVendorIes;
@Before
public void setUp() {
@@ -59,6 +60,9 @@ public class SingleScanSettingsTest {
mHiddenNetwork1.ssid = TEST_SSID_1;
mHiddenNetwork2 = new HiddenNetwork();
mHiddenNetwork2.ssid = TEST_SSID_2;
+
+ mVendorIes = new byte[]{(byte) 0xdd, 0x7, 0x00, 0x50, (byte) 0xf2, 0x08, 0x11, 0x22, 0x33,
+ (byte) 0xdd, 0x7, 0x00, 0x50, (byte) 0xf2, 0x08, 0x44, 0x55, 0x66};
}
/**
@@ -69,12 +73,12 @@ public class SingleScanSettingsTest {
public void canSerializeAndDeserialize() {
SingleScanSettings scanSettings = new SingleScanSettings();
scanSettings.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
-
scanSettings.channelSettings =
new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
scanSettings.hiddenNetworks =
new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
scanSettings.enable6GhzRnr = true;
+ scanSettings.vendorIes = mVendorIes;
Parcel parcel = Parcel.obtain();
scanSettings.writeToParcel(parcel, 0);
@@ -98,6 +102,7 @@ public class SingleScanSettingsTest {
new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
scanSettings1.hiddenNetworks =
new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+ scanSettings1.vendorIes = mVendorIes;
SingleScanSettings scanSettings2 = new SingleScanSettings();
scanSettings2.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
@@ -105,6 +110,7 @@ public class SingleScanSettingsTest {
new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
scanSettings2.hiddenNetworks =
new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+ scanSettings2.vendorIes = mVendorIes;
assertEquals(scanSettings1, scanSettings2);
assertEquals(scanSettings1.hashCode(), scanSettings2.hashCode());
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 50126226eb94..362eb1425a30 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -68,6 +68,7 @@ import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -127,6 +128,9 @@ public class WifiNl80211ManagerTest {
private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
private static final int[] TEST_FREQUENCIES_1 = {};
private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+ private static final byte[] TEST_VENDOR_IES =
+ new byte[]{(byte) 0xdd, 0x7, 0x00, 0x50, (byte) 0xf2, 0x08, 0x11, 0x22, 0x33,
+ (byte) 0xdd, 0x7, 0x00, 0x50, (byte) 0xf2, 0x08, 0x44, 0x55, 0x66};
private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
@@ -512,7 +516,7 @@ public class WifiNl80211ManagerTest {
SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
- SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false)));
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false, null)));
}
/**
@@ -523,12 +527,13 @@ public class WifiNl80211ManagerTest {
when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
Bundle bundle = new Bundle();
bundle.putBoolean(WifiNl80211Manager.SCANNING_PARAM_ENABLE_6GHZ_RNR, true);
+ bundle.putByteArray(WifiNl80211Manager.EXTRA_SCANNING_PARAM_VENDOR_IES, TEST_VENDOR_IES);
assertTrue(mWificondControl.startScan(
TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, bundle));
verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
- SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, true)));
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, true, TEST_VENDOR_IES)));
}
/**
@@ -542,7 +547,7 @@ public class WifiNl80211ManagerTest {
SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, null));
verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
- SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false)));
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false, null)));
}
/**
@@ -556,7 +561,7 @@ public class WifiNl80211ManagerTest {
SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, new Bundle()));
verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
- SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false)));
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false, null)));
}
/**
@@ -577,7 +582,7 @@ public class WifiNl80211ManagerTest {
// But the argument passed down should have the duplicate removed.
verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
- SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false)));
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false, null)));
}
/**
@@ -589,7 +594,7 @@ public class WifiNl80211ManagerTest {
assertTrue(mWificondControl.startScan(
TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, null, null));
verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
- IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null, false)));
+ IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null, false, null)));
}
/**
@@ -1161,13 +1166,15 @@ public class WifiNl80211ManagerTest {
private final Set<Integer> mExpectedFreqs;
private final List<byte[]> mExpectedSsids;
private final boolean mExpectedEnable6GhzRnr;
+ private final byte[] mExpectedVendorIes;
ScanMatcher(int expectedScanType, Set<Integer> expectedFreqs, List<byte[]> expectedSsids,
- boolean expectedEnable6GhzRnr) {
+ boolean expectedEnable6GhzRnr, byte[] expectedVendorIes) {
this.mExpectedScanType = expectedScanType;
this.mExpectedFreqs = expectedFreqs;
this.mExpectedSsids = expectedSsids;
this.mExpectedEnable6GhzRnr = expectedEnable6GhzRnr;
+ this.mExpectedVendorIes = expectedVendorIes;
}
@Override
@@ -1202,12 +1209,15 @@ public class WifiNl80211ManagerTest {
if (!mExpectedSsids.equals(ssidSet)) {
return false;
}
-
} else {
if (hiddenNetworks != null && hiddenNetworks.size() > 0) {
return false;
}
}
+
+ if (!Arrays.equals(mExpectedVendorIes, settings.vendorIes)) {
+ return false;
+ }
return true;
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS b/wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS
new file mode 100644
index 000000000000..8873d0714a84
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/OWNERS
@@ -0,0 +1 @@
+file:/wifi/java/src/android/net/wifi/sharedconnectivity/OWNERS
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
new file mode 100644
index 000000000000..c6f67987746a
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2023 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.sharedconnectivity.service;
+
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP;
+import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP;
+import static android.net.wifi.sharedconnectivity.app.HotspotNetwork.NETWORK_TYPE_CELLULAR;
+import static android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN;
+import static android.net.wifi.sharedconnectivity.app.KnownNetwork.NETWORK_SOURCE_NEARBY_SELF;
+import static android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus.CONNECTION_STATUS_SAVED;
+import static android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.DEVICE_TYPE_TABLET;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
+import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.KnownNetwork;
+import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
+import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Unit tests for {@link SharedConnectivityService}.
+ */
+@SmallTest
+public class SharedConnectivityServiceTest {
+ private static final int LATCH_TIMEOUT = 2;
+
+ private static final NetworkProviderInfo NETWORK_PROVIDER_INFO =
+ new NetworkProviderInfo.Builder("TEST_NAME", "TEST_MODEL")
+ .setDeviceType(DEVICE_TYPE_TABLET).setConnectionStrength(2)
+ .setBatteryPercentage(50).build();
+ private static final HotspotNetwork HOTSPOT_NETWORK =
+ new HotspotNetwork.Builder().setDeviceId(1).setNetworkProviderInfo(
+ NETWORK_PROVIDER_INFO)
+ .setHostNetworkType(NETWORK_TYPE_CELLULAR).setNetworkName("TEST_NETWORK")
+ .setHotspotSsid("TEST_SSID").setHotspotBssid("TEST_BSSID")
+ .addHotspotSecurityType(SECURITY_TYPE_WEP)
+ .addHotspotSecurityType(SECURITY_TYPE_EAP).build();
+ private static final List<HotspotNetwork> HOTSPOT_NETWORKS = List.of(HOTSPOT_NETWORK);
+ private static final KnownNetwork KNOWN_NETWORK =
+ new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE_NEARBY_SELF)
+ .setSsid("TEST_SSID").addSecurityType(SECURITY_TYPE_WEP)
+ .addSecurityType(SECURITY_TYPE_EAP).setNetworkProviderInfo(
+ NETWORK_PROVIDER_INFO).build();
+ private static final List<KnownNetwork> KNOWN_NETWORKS = List.of(KNOWN_NETWORK);
+ private static final HotspotNetworkConnectionStatus HOTSPOT_NETWORK_CONNECTION_STATUS =
+ new HotspotNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_UNKNOWN)
+ .setHotspotNetwork(HOTSPOT_NETWORK).setExtras(Bundle.EMPTY).build();
+ private static final KnownNetworkConnectionStatus KNOWN_NETWORK_CONNECTION_STATUS =
+ new KnownNetworkConnectionStatus.Builder().setStatus(CONNECTION_STATUS_SAVED)
+ .setKnownNetwork(KNOWN_NETWORK).setExtras(Bundle.EMPTY).build();
+
+ @Mock
+ Context mContext;
+
+ @Mock
+ Resources mResources;
+
+ @Mock
+ ISharedConnectivityCallback mCallback;
+
+ @Mock
+ IBinder mBinder;
+
+ static class FakeSharedConnectivityService extends SharedConnectivityService {
+ public void attachBaseContext(Context context) {
+ super.attachBaseContext(context);
+ }
+
+ private HotspotNetwork mConnectedHotspotNetwork;
+ private HotspotNetwork mDisconnectedHotspotNetwork;
+ private KnownNetwork mConnectedKnownNetwork;
+ private KnownNetwork mForgottenKnownNetwork;
+ private CountDownLatch mLatch;
+
+ public HotspotNetwork getConnectedHotspotNetwork() {
+ return mConnectedHotspotNetwork;
+ }
+
+ public HotspotNetwork getDisconnectedHotspotNetwork() {
+ return mDisconnectedHotspotNetwork;
+ }
+
+ public KnownNetwork getConnectedKnownNetwork() {
+ return mConnectedKnownNetwork;
+ }
+
+ public KnownNetwork getForgottenKnownNetwork() {
+ return mForgottenKnownNetwork;
+ }
+
+ public void initializeLatch() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ public CountDownLatch getLatch() {
+ return mLatch;
+ }
+
+ @Override
+ public void onConnectHotspotNetwork(@NonNull HotspotNetwork network) {
+ mConnectedHotspotNetwork = network;
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onDisconnectHotspotNetwork(@NonNull HotspotNetwork network) {
+ mDisconnectedHotspotNetwork = network;
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onConnectKnownNetwork(@NonNull KnownNetwork network) {
+ mConnectedKnownNetwork = network;
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onForgetKnownNetwork(@NonNull KnownNetwork network) {
+ mForgottenKnownNetwork = network;
+ if (mLatch != null) {
+ mLatch.countDown();
+ }
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+ }
+
+ @Test
+ public void onBind_isNotNull() {
+ SharedConnectivityService service = createService();
+
+ assertThat(service.onBind(new Intent())).isNotNull();
+ }
+
+ @Test
+ public void getHotspotNetworks() throws RemoteException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+ service.setHotspotNetworks(HOTSPOT_NETWORKS);
+
+ assertThat(binder.getHotspotNetworks())
+ .containsExactlyElementsIn(List.copyOf(HOTSPOT_NETWORKS));
+ }
+
+ @Test
+ public void getKnownNetworks() throws RemoteException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+ service.setKnownNetworks(KNOWN_NETWORKS);
+
+ assertThat(binder.getKnownNetworks())
+ .containsExactlyElementsIn(List.copyOf(KNOWN_NETWORKS));
+ }
+
+ @Test
+ public void getSharedConnectivitySettingsState() throws RemoteException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
+
+ service.setSettingsState(buildSettingsState());
+
+ assertThat(binder.getSettingsState()).isEqualTo(buildSettingsState());
+ }
+
+ @Test
+ public void updateHotspotNetworkConnectionStatus() throws RemoteException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+ service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
+
+ assertThat(binder.getHotspotNetworkConnectionStatus())
+ .isEqualTo(HOTSPOT_NETWORK_CONNECTION_STATUS);
+ }
+
+ @Test
+ public void updateKnownNetworkConnectionStatus() throws RemoteException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+
+ service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
+
+ assertThat(binder.getKnownNetworkConnectionStatus())
+ .isEqualTo(KNOWN_NETWORK_CONNECTION_STATUS);
+ }
+
+ @Test
+ public void areHotspotNetworksEnabledForService() {
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getPackageName()).thenReturn("package");
+ when(mResources.getString(anyInt())).thenReturn("package");
+ when(mResources.getBoolean(anyInt())).thenReturn(true);
+
+ assertThat(SharedConnectivityService.areHotspotNetworksEnabledForService(mContext))
+ .isTrue();
+ }
+
+ @Test
+ public void areHotspotNetworksEnabledForService_notSamePackage_shouldReturnFalse() {
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getPackageName()).thenReturn("package");
+ when(mResources.getString(anyInt())).thenReturn("other_package");
+ when(mResources.getBoolean(anyInt())).thenReturn(true);
+
+ assertThat(SharedConnectivityService.areHotspotNetworksEnabledForService(mContext))
+ .isFalse();
+ }
+
+ @Test
+ public void areKnownNetworksEnabledForService() {
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getPackageName()).thenReturn("package");
+ when(mResources.getString(anyInt())).thenReturn("package");
+ when(mResources.getBoolean(anyInt())).thenReturn(true);
+
+ assertThat(SharedConnectivityService.areKnownNetworksEnabledForService(mContext)).isTrue();
+ }
+
+ @Test
+ public void areKnownNetworksEnabledForService_notSamePackage_shouldReturnFalse() {
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getPackageName()).thenReturn("package");
+ when(mResources.getString(anyInt())).thenReturn("other_package");
+ when(mResources.getBoolean(anyInt())).thenReturn(true);
+
+ assertThat(SharedConnectivityService.areKnownNetworksEnabledForService(mContext)).isFalse();
+ }
+
+ @Test
+ public void connectHotspotNetwork() throws RemoteException, InterruptedException {
+ FakeSharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ service.initializeLatch();
+
+ binder.connectHotspotNetwork(HOTSPOT_NETWORK);
+
+ assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ assertThat(service.getConnectedHotspotNetwork()).isEqualTo(HOTSPOT_NETWORK);
+ }
+
+ @Test
+ public void disconnectHotspotNetwork() throws RemoteException, InterruptedException {
+ FakeSharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ service.initializeLatch();
+
+ binder.disconnectHotspotNetwork(HOTSPOT_NETWORK);
+
+ assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ assertThat(service.getDisconnectedHotspotNetwork()).isEqualTo(HOTSPOT_NETWORK);
+ }
+
+ @Test
+ public void connectKnownNetwork() throws RemoteException , InterruptedException {
+ FakeSharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ service.initializeLatch();
+
+ binder.connectKnownNetwork(KNOWN_NETWORK);
+
+ assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ assertThat(service.getConnectedKnownNetwork()).isEqualTo(KNOWN_NETWORK);
+ }
+
+ @Test
+ public void forgetKnownNetwork() throws RemoteException, InterruptedException {
+ FakeSharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ service.initializeLatch();
+
+ binder.forgetKnownNetwork(KNOWN_NETWORK);
+
+ assertThat(service.getLatch().await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ assertThat(service.getForgottenKnownNetwork()).isEqualTo(KNOWN_NETWORK);
+ }
+
+ @Test
+ public void registerCallback() throws RemoteException, InterruptedException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ when(mCallback.asBinder()).thenReturn(mBinder);
+ when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
+ SharedConnectivitySettingsState state = buildSettingsState();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ service.setCountdownLatch(latch);
+ binder.registerCallback(mCallback);
+ assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ service.setHotspotNetworks(HOTSPOT_NETWORKS);
+ service.setKnownNetworks(KNOWN_NETWORKS);
+ service.setSettingsState(state);
+ service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
+ service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
+
+ verify(mCallback).onHotspotNetworksUpdated(HOTSPOT_NETWORKS);
+ verify(mCallback).onKnownNetworksUpdated(KNOWN_NETWORKS);
+ verify(mCallback).onSharedConnectivitySettingsChanged(state);
+ verify(mCallback).onHotspotNetworkConnectionStatusChanged(
+ HOTSPOT_NETWORK_CONNECTION_STATUS);
+ verify(mCallback).onKnownNetworkConnectionStatusChanged(KNOWN_NETWORK_CONNECTION_STATUS);
+ }
+
+ @Test
+ public void unregisterCallback() throws RemoteException, InterruptedException {
+ SharedConnectivityService service = createService();
+ ISharedConnectivityService.Stub binder =
+ (ISharedConnectivityService.Stub) service.onBind(new Intent());
+ when(mCallback.asBinder()).thenReturn(mBinder);
+ when(mContext.getPackageName()).thenReturn("android.net.wifi.nonupdatable.test");
+
+ CountDownLatch latch = new CountDownLatch(1);
+ service.setCountdownLatch(latch);
+ binder.registerCallback(mCallback);
+ assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ latch = new CountDownLatch(1);
+ service.setCountdownLatch(latch);
+ binder.unregisterCallback(mCallback);
+ assertThat(latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)).isTrue();
+ service.setHotspotNetworks(HOTSPOT_NETWORKS);
+ service.setKnownNetworks(KNOWN_NETWORKS);
+ service.setSettingsState(buildSettingsState());
+ service.updateHotspotNetworkConnectionStatus(HOTSPOT_NETWORK_CONNECTION_STATUS);
+ service.updateKnownNetworkConnectionStatus(KNOWN_NETWORK_CONNECTION_STATUS);
+
+ verify(mCallback, never()).onHotspotNetworksUpdated(any());
+ verify(mCallback, never()).onKnownNetworksUpdated(any());
+ verify(mCallback, never()).onSharedConnectivitySettingsChanged(any());
+ verify(mCallback, never()).onHotspotNetworkConnectionStatusChanged(any());
+ verify(mCallback, never()).onKnownNetworkConnectionStatusChanged(any());
+ }
+
+ private FakeSharedConnectivityService createService() {
+ FakeSharedConnectivityService service = new FakeSharedConnectivityService();
+ service.attachBaseContext(mContext);
+ return service;
+ }
+
+ private SharedConnectivitySettingsState buildSettingsState() {
+ return new SharedConnectivitySettingsState.Builder().setInstantTetherEnabled(true)
+ .setInstantTetherSettingsPendingIntent(
+ PendingIntent.getActivity(mContext, 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE))
+ .setExtras(Bundle.EMPTY).build();
+ }
+}