diff options
Diffstat (limited to 'wifi')
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(); + } +} |