summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Quang Luong <qal@google.com> 2020-11-10 12:04:37 -0800
committer Quang Luong <qal@google.com> 2020-11-17 13:49:41 -0800
commit1459f65a7ce327fa7e7301e194cb5089afbde801 (patch)
tree921d08d9185b9b98b706f3d468ce32d1d6336921
parenta3bac99dc42f10944c47411de9867218dbec6618 (diff)
Add APIs for Wifi/Cellular coex channel avoidance
Added SystemApis for getting and setting the unsafe channels to avoid for framework Wifi/Cellular coex channel avoidance. Bug: 153651001 Test: atest WifiManagerTest, atest CoexUnsafeChannelTest Change-Id: I4cb4137766c23f096951a2a5a4df1bd946f6c446
-rw-r--r--core/api/system-current.txt2
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl19
-rw-r--r--wifi/api/system-current.txt24
-rw-r--r--wifi/java/android/net/wifi/CoexUnsafeChannel.java176
-rw-r--r--wifi/java/android/net/wifi/ICoexCallback.aidl26
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl12
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java234
-rw-r--r--wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java97
-rw-r--r--wifi/tests/src/android/net/wifi/WifiManagerTest.java147
10 files changed, 746 insertions, 1 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 05dbf0000b74..a9e57d25ed56 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -247,7 +247,9 @@ package android {
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
+ field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_SET_DEVICE_MOBILITY_STATE = "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE";
+ field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ea667277efee..298694fd48f3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1809,6 +1809,16 @@
<permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows system APK to update Wifi/Cellular coex channels to avoid.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows applications to access Wifi/Cellular coex channels being avoided.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
+ android:protectionLevel="signature|privileged" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
diff --git a/wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl b/wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl
new file mode 100644
index 000000000000..cb359e9b2c1b
--- /dev/null
+++ b/wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+parcelable CoexUnsafeChannel;
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index c4a1766f982c..3e3d1eee536d 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -1,6 +1,17 @@
// Signature format: 2.0
package android.net.wifi {
+ public final class CoexUnsafeChannel implements android.os.Parcelable {
+ ctor public CoexUnsafeChannel(int, int);
+ ctor public CoexUnsafeChannel(int, int, int);
+ method public int getBand();
+ method public int getChannel();
+ method public int getPowerCapDbm();
+ method public boolean isPowerCapAvailable();
+ method public void setPowerCapDbm(int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.CoexUnsafeChannel> CREATOR;
+ }
+
public abstract class EasyConnectStatusCallback {
ctor public EasyConnectStatusCallback();
method public abstract void onConfiguratorSuccess(int);
@@ -455,6 +466,8 @@ package android.net.wifi {
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void factoryReset();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void forget(int, @Nullable android.net.wifi.WifiManager.ActionListener);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
+ method @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public int getCoexRestrictions();
+ method @NonNull @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public java.util.Set<android.net.wifi.CoexUnsafeChannel> getCoexUnsafeChannels();
method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCountryCode();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.Network getCurrentNetwork();
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String[] getFactoryMacAddresses();
@@ -475,6 +488,7 @@ package android.net.wifi {
method public boolean isVerboseLoggingEnabled();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
+ method @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public void registerCoexCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.CoexCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerNetworkRequestMatchCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerTrafficStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.TrafficStateCallback);
@@ -486,6 +500,7 @@ package android.net.wifi {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAutoWakeupEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS) public void setCoexUnsafeChannels(@NonNull java.util.Set<android.net.wifi.CoexUnsafeChannel>, int);
method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setPasspointMeteredOverride(@NonNull String, int);
@@ -505,6 +520,7 @@ package android.net.wifi {
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean stopSoftAp();
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void stopTemporarilyDisablingAllNonCarrierMergedWifi();
+ method @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS) public void unregisterCoexCallback(@NonNull android.net.wifi.WifiManager.CoexCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterNetworkRequestMatchCallback(@NonNull android.net.wifi.WifiManager.NetworkRequestMatchCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterSoftApCallback(@NonNull android.net.wifi.WifiManager.SoftApCallback);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void unregisterTrafficStateCallback(@NonNull android.net.wifi.WifiManager.TrafficStateCallback);
@@ -518,6 +534,9 @@ package android.net.wifi {
field public static final int CHANGE_REASON_ADDED = 0; // 0x0
field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
field public static final int CHANGE_REASON_REMOVED = 1; // 0x1
+ field public static final int COEX_RESTRICTION_SOFTAP = 2; // 0x2
+ field public static final int COEX_RESTRICTION_WIFI_AWARE = 4; // 0x4
+ field public static final int COEX_RESTRICTION_WIFI_DIRECT = 1; // 0x1
field public static final String CONFIGURED_NETWORKS_CHANGED_ACTION = "android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
field public static final int DEVICE_MOBILITY_STATE_HIGH_MVMT = 1; // 0x1
field public static final int DEVICE_MOBILITY_STATE_LOW_MVMT = 2; // 0x2
@@ -565,6 +584,11 @@ package android.net.wifi {
method public void onSuccess();
}
+ public abstract static class WifiManager.CoexCallback {
+ ctor public WifiManager.CoexCallback();
+ method public abstract void onCoexUnsafeChannelsChanged();
+ }
+
public static interface WifiManager.NetworkRequestMatchCallback {
method public default void onAbort();
method public default void onMatch(@NonNull java.util.List<android.net.wifi.ScanResult>);
diff --git a/wifi/java/android/net/wifi/CoexUnsafeChannel.java b/wifi/java/android/net/wifi/CoexUnsafeChannel.java
new file mode 100644
index 000000000000..3f9efa020d05
--- /dev/null
+++ b/wifi/java/android/net/wifi/CoexUnsafeChannel.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2020 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 static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
+import static android.net.wifi.WifiScanner.WIFI_BAND_6_GHZ;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Data structure class representing a Wi-Fi channel that would cause interference to/receive
+ * interference from the active cellular channels and should be avoided.
+ *
+ * If {@link #isPowerCapAvailable()} is {@code true}, then a valid power cap value is available
+ * through {@link #getPowerCapDbm()} to be used if this channel cannot be avoided. If {@code false},
+ * then {@link #getPowerCapDbm()} throws an IllegalStateException and the channel will not need to
+ * cap its power.
+ *
+ * @hide
+ */
+@SystemApi
+public final class CoexUnsafeChannel implements Parcelable {
+ private @WifiAnnotations.WifiBandBasic int mBand;
+ private int mChannel;
+ private boolean mIsPowerCapAvailable = false;
+ private int mPowerCapDbm;
+
+ /**
+ * Constructor for a CoexUnsafeChannel with no power cap specified.
+ * @param band One of {@link WifiAnnotations.WifiBandBasic}
+ * @param channel Channel number
+ */
+ public CoexUnsafeChannel(@WifiAnnotations.WifiBandBasic int band, int channel) {
+ mBand = band;
+ mChannel = channel;
+ }
+
+ /**
+ * Constructor for a CoexUnsafeChannel with power cap specified.
+ * @param band One of {@link WifiAnnotations.WifiBandBasic}
+ * @param channel Channel number
+ * @param powerCapDbm Power cap in dBm
+ */
+ public CoexUnsafeChannel(@WifiAnnotations.WifiBandBasic int band, int channel,
+ int powerCapDbm) {
+ mBand = band;
+ mChannel = channel;
+ setPowerCapDbm(powerCapDbm);
+ }
+
+ /** Returns the Wi-Fi band of this channel as one of {@link WifiAnnotations.WifiBandBasic} */
+ public @WifiAnnotations.WifiBandBasic int getBand() {
+ return mBand;
+ }
+
+ /** Returns the channel number of this channel. */
+ public int getChannel() {
+ return mChannel;
+ }
+
+ /** Returns {@code true} if {@link #getPowerCapDbm()} is a valid value, else {@code false} */
+ public boolean isPowerCapAvailable() {
+ return mIsPowerCapAvailable;
+ }
+
+ /**
+ * Returns the power cap of this channel in dBm. Throws IllegalStateException if
+ * {@link #isPowerCapAvailable()} is {@code false}.
+ */
+ public int getPowerCapDbm() {
+ if (!mIsPowerCapAvailable) {
+ throw new IllegalStateException("getPowerCapDbm called but power cap is unavailable");
+ }
+ return mPowerCapDbm;
+ }
+
+ /** Set the power cap of this channel. */
+ public void setPowerCapDbm(int powerCapDbm) {
+ mIsPowerCapAvailable = true;
+ mPowerCapDbm = powerCapDbm;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CoexUnsafeChannel that = (CoexUnsafeChannel) o;
+ return mBand == that.mBand
+ && mChannel == that.mChannel
+ && mIsPowerCapAvailable == that.mIsPowerCapAvailable
+ && mPowerCapDbm == that.mPowerCapDbm;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBand, mChannel, mIsPowerCapAvailable, mPowerCapDbm);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sj = new StringBuilder("CoexUnsafeChannel{");
+ sj.append(mChannel);
+ sj.append(", ");
+ if (mBand == WIFI_BAND_24_GHZ) {
+ sj.append("2.4GHz");
+ } else if (mBand == WIFI_BAND_5_GHZ) {
+ sj.append("5GHz");
+ } else if (mBand == WIFI_BAND_6_GHZ) {
+ sj.append("6GHz");
+ } else {
+ sj.append("UNKNOWN BAND");
+ }
+ if (mIsPowerCapAvailable) {
+ sj.append(", ").append(mPowerCapDbm).append("dBm");
+ }
+ sj.append('}');
+ return sj.toString();
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mBand);
+ dest.writeInt(mChannel);
+ dest.writeBoolean(mIsPowerCapAvailable);
+ if (mIsPowerCapAvailable) {
+ dest.writeInt(mPowerCapDbm);
+ }
+ }
+
+ /** Implement the Parcelable interface */
+ public static final @NonNull Creator<CoexUnsafeChannel> CREATOR =
+ new Creator<CoexUnsafeChannel>() {
+ public CoexUnsafeChannel createFromParcel(Parcel in) {
+ final int band = in.readInt();
+ final int channel = in.readInt();
+ final boolean isPowerCapAvailable = in.readBoolean();
+ if (isPowerCapAvailable) {
+ final int powerCapDbm = in.readInt();
+ return new CoexUnsafeChannel(band, channel, powerCapDbm);
+ }
+ return new CoexUnsafeChannel(band, channel);
+ }
+
+ public CoexUnsafeChannel[] newArray(int size) {
+ return new CoexUnsafeChannel[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/ICoexCallback.aidl b/wifi/java/android/net/wifi/ICoexCallback.aidl
new file mode 100644
index 000000000000..89e4c4b93013
--- /dev/null
+++ b/wifi/java/android/net/wifi/ICoexCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 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;
+
+/**
+ * Interface for Wi-Fi/cellular coex callback.
+ * @hide
+ */
+oneway interface ICoexCallback
+{
+ void onCoexUnsafeChannelsChanged();
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index cc864eafcff1..10184b728bb5 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -24,7 +24,9 @@ import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.DhcpInfo;
import android.net.Network;
+import android.net.wifi.CoexUnsafeChannel;
import android.net.wifi.IActionListener;
+import android.net.wifi.ICoexCallback;
import android.net.wifi.IDppCallback;
import android.net.wifi.ILocalOnlyHotspotCallback;
import android.net.wifi.INetworkRequestMatchCallback;
@@ -144,6 +146,16 @@ interface IWifiManager
void updateInterfaceIpState(String ifaceName, int mode);
+ void setCoexUnsafeChannels(in List<CoexUnsafeChannel> unsafeChannels, int mandatoryRestrictions);
+
+ List<CoexUnsafeChannel> getCoexUnsafeChannels();
+
+ int getCoexRestrictions();
+
+ void registerCoexCallback(in ICoexCallback callback);
+
+ void unregisterCoexCallback(in ICoexCallback callback);
+
boolean startSoftAp(in WifiConfiguration wifiConfig, String packageName);
boolean startTetheredHotspot(in SoftApConfiguration softApConfig, String packageName);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 9db50b3f9c15..06d81d99d3ce 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -72,6 +72,7 @@ import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -3110,6 +3111,238 @@ public class WifiManager {
}
}
+ /* Wi-Fi/Cellular Coex */
+
+ /**
+ * Mandatory coex restriction flag for Wi-Fi Direct.
+ *
+ * @see #setCoexUnsafeChannels(Set, int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int COEX_RESTRICTION_WIFI_DIRECT = 0x1 << 0;
+
+ /**
+ * Mandatory coex restriction flag for SoftAP
+ *
+ * @see #setCoexUnsafeChannels(Set, int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int COEX_RESTRICTION_SOFTAP = 0x1 << 1;
+
+ /**
+ * Mandatory coex restriction flag for Wi-Fi Aware.
+ *
+ * @see #setCoexUnsafeChannels(Set, int)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int COEX_RESTRICTION_WIFI_AWARE = 0x1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"COEX_RESTRICTION_"}, value = {
+ COEX_RESTRICTION_WIFI_DIRECT,
+ COEX_RESTRICTION_SOFTAP,
+ COEX_RESTRICTION_WIFI_AWARE
+ })
+ public @interface CoexRestriction {}
+
+ /**
+ * Specify the set of {@link CoexUnsafeChannel} to propagate through the framework for
+ * Wi-Fi/Cellular coex channel avoidance if the default algorithm is disabled via overlay
+ * (i.e. config_wifiCoexDefaultAlgorithmEnabled = false). Otherwise do nothing.
+ *
+ * @param unsafeChannels Set of {@link CoexUnsafeChannel} to avoid.
+ * @param restrictions Bitmap of {@link CoexRestriction} specifying the mandatory restricted
+ * uses of the specified channels. If any restrictions are set, then the
+ * supplied CoexUnsafeChannels will be completely avoided for the
+ * specified modes, rather than be avoided with best effort.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS)
+ public void setCoexUnsafeChannels(@NonNull Set<CoexUnsafeChannel> unsafeChannels,
+ int restrictions) {
+ if (unsafeChannels == null) {
+ throw new IllegalArgumentException("unsafeChannels must not be null");
+ }
+ try {
+ mService.setCoexUnsafeChannels(new ArrayList<>(unsafeChannels), restrictions);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the set of current {@link CoexUnsafeChannel} being used for Wi-Fi/Cellular coex
+ * channel avoidance.
+ *
+ * This returns the set calculated by the default algorithm if
+ * config_wifiCoexDefaultAlgorithmEnabled is {@code true}. Otherwise, returns the set supplied
+ * in {@link #setCoexUnsafeChannels(Set, int)}.
+ *
+ * If any {@link CoexRestriction} flags are set in {@link #getCoexRestrictions()}, then the
+ * CoexUnsafeChannels should be totally avoided (i.e. not best effort) for the Wi-Fi modes
+ * specified by the flags.
+ *
+ * @return Set of current CoexUnsafeChannels.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+ public Set<CoexUnsafeChannel> getCoexUnsafeChannels() {
+ try {
+ return new HashSet<>(mService.getCoexUnsafeChannels());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the current coex restrictions being used for Wi-Fi/Cellular coex
+ * channel avoidance.
+ *
+ * This returns the restrictions calculated by the default algorithm if
+ * config_wifiCoexDefaultAlgorithmEnabled is {@code true}. Otherwise, returns the value supplied
+ * in {@link #setCoexUnsafeChannels(Set, int)}.
+ *
+ * @return int containing a bitwise-OR combination of {@link CoexRestriction}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+ public int getCoexRestrictions() {
+ try {
+ return mService.getCoexRestrictions();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a CoexCallback to listen on the current CoexUnsafeChannels and restrictions being
+ * used for Wi-Fi/cellular coex channel avoidance.
+ * @param executor Executor to execute listener callback on
+ * @param callback CoexCallback to register
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+ public void registerCoexCallback(
+ @NonNull @CallbackExecutor Executor executor, @NonNull CoexCallback callback) {
+ if (executor == null) throw new IllegalArgumentException("executor must not be null");
+ if (callback == null) throw new IllegalArgumentException("callback must not be null");
+ CoexCallback.CoexCallbackProxy proxy = callback.getProxy();
+ proxy.initProxy(executor, callback);
+ try {
+ mService.registerCoexCallback(proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters a CoexCallback from listening on the current CoexUnsafeChannels and restrictions
+ * being used for Wi-Fi/cellular coex channel avoidance.
+ * @param callback CoexCallback to unregister
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS)
+ public void unregisterCoexCallback(@NonNull CoexCallback callback) {
+ if (callback == null) throw new IllegalArgumentException("callback must not be null");
+ CoexCallback.CoexCallbackProxy proxy = callback.getProxy();
+ try {
+ mService.unregisterCoexCallback(proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ proxy.cleanUpProxy();
+ }
+ }
+
+ /**
+ * Abstract callback class for applications to receive updates about current CoexUnsafeChannels
+ * for Wi-Fi/Cellular coex channel avoidance.
+ *
+ * @hide
+ */
+ @SystemApi
+ public abstract static class CoexCallback {
+ private final CoexCallbackProxy mCoexCallbackProxy;
+
+ public CoexCallback() {
+ mCoexCallbackProxy = new CoexCallbackProxy();
+ }
+
+ /*package*/ @NonNull
+ CoexCallbackProxy getProxy() {
+ return mCoexCallbackProxy;
+ }
+
+ /**
+ * Indicates that the current CoexUnsafeChannels or restrictions have changed.
+ * Clients should call {@link #getCoexUnsafeChannels()} and {@link #getCoexRestrictions()}
+ * to get the updated values.
+ */
+ public abstract void onCoexUnsafeChannelsChanged();
+
+ /**
+ * Callback proxy for CoexCallback objects.
+ */
+ private static class CoexCallbackProxy extends ICoexCallback.Stub {
+ private final Object mLock = new Object();
+ @Nullable @GuardedBy("mLock") private Executor mExecutor;
+ @Nullable @GuardedBy("mLock") private CoexCallback mCallback;
+
+ CoexCallbackProxy() {
+ mExecutor = null;
+ mCallback = null;
+ }
+
+ /*package*/ void initProxy(@NonNull Executor executor,
+ @NonNull CoexCallback callback) {
+ synchronized (mLock) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+ }
+
+ /*package*/ void cleanUpProxy() {
+ synchronized (mLock) {
+ mExecutor = null;
+ mCallback = null;
+ }
+ }
+
+ @Override
+ public void onCoexUnsafeChannelsChanged() {
+ Executor executor;
+ CoexCallback callback;
+ synchronized (mLock) {
+ executor = mExecutor;
+ callback = mCallback;
+ }
+ if (executor == null || callback == null) {
+ return;
+ }
+ Binder.clearCallingIdentity();
+ executor.execute(callback::onCoexUnsafeChannelsChanged);
+ }
+ }
+ }
+
/**
* Start Soft AP (hotspot) mode for tethering purposes with the specified configuration.
* Note that starting Soft AP mode may disable station mode operation if the device does not
@@ -5853,7 +6086,6 @@ public class WifiManager {
executor.execute(callback::onScanResultsAvailable);
}
}
-
}
/**
diff --git a/wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java b/wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java
new file mode 100644
index 000000000000..320f25e715fe
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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 static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.CoexUnsafeChannel}.
+ */
+@SmallTest
+public class CoexUnsafeChannelTest {
+ /**
+ * Verifies {@link CoexUnsafeChannel#isPowerCapAvailable()} returns false if no cap is set.
+ */
+ @Test
+ public void testIsPowerCapAvailable_noPowerCap_returnsFalse() {
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+
+ assertThat(unsafeChannel.isPowerCapAvailable()).isFalse();
+ }
+
+ /**
+ * Verifies {@link CoexUnsafeChannel#isPowerCapAvailable()} returns true if a cap is set, and
+ * {@link CoexUnsafeChannel#getPowerCapDbm()} returns the set value.
+ */
+ @Test
+ public void testIsPowerCapAvailable_powerCapSet_returnsTrue() {
+ final int powerCapDbm = -50;
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+
+ unsafeChannel.setPowerCapDbm(powerCapDbm);
+
+ assertThat(unsafeChannel.isPowerCapAvailable()).isTrue();
+ assertThat(unsafeChannel.getPowerCapDbm()).isEqualTo(powerCapDbm);
+ }
+
+ /**
+ * Verifies {@link CoexUnsafeChannel#getPowerCapDbm()} throws an IllegalStateException if
+ * {@link CoexUnsafeChannel#isPowerCapAvailable()} is {@code false}.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testGetPowerCap_powerCapUnavailable_throwsException() {
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+
+ unsafeChannel.getPowerCapDbm();
+ }
+
+ /**
+ * Verify parcel read/write for CoexUnsafeChannel with or without power cap.
+ */
+ @Test
+ public void testParcelReadWrite_withOrWithoutCap_readEqualsWritten() throws Exception {
+ CoexUnsafeChannel writeUnsafeChannelNoCap =
+ new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6);
+ CoexUnsafeChannel writeUnsafeChannelCapped =
+ new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6, -50);
+
+ CoexUnsafeChannel readUnsafeChannelNoCap = parcelReadWrite(writeUnsafeChannelNoCap);
+ CoexUnsafeChannel readUnsafeChannelCapped = parcelReadWrite(writeUnsafeChannelCapped);
+
+ assertThat(writeUnsafeChannelNoCap).isEqualTo(readUnsafeChannelNoCap);
+ assertThat(writeUnsafeChannelCapped).isEqualTo(readUnsafeChannelCapped);
+ }
+
+ /**
+ * Write the provided {@link CoexUnsafeChannel} to a parcel and deserialize it.
+ */
+ private static CoexUnsafeChannel parcelReadWrite(CoexUnsafeChannel writeResult)
+ throws Exception {
+ Parcel parcel = Parcel.obtain();
+ writeResult.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ return CoexUnsafeChannel.CREATOR.createFromParcel(parcel);
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index aefebbcec607..39f6f57b05b3 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -19,6 +19,9 @@ package android.net.wifi;
import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
import static android.net.wifi.WifiManager.ActionListener;
import static android.net.wifi.WifiManager.BUSY;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_SOFTAP;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_AWARE;
+import static android.net.wifi.WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
import static android.net.wifi.WifiManager.ERROR;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
@@ -43,6 +46,7 @@ import static android.net.wifi.WifiManager.WIFI_FEATURE_SCANNER;
import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SAE;
import static android.net.wifi.WifiManager.WIFI_FEATURE_WPA3_SUITE_B;
import static android.net.wifi.WifiManager.WpsCallback;
+import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -74,6 +78,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.DhcpInfo;
import android.net.MacAddress;
+import android.net.wifi.WifiManager.CoexCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -108,9 +113,11 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -151,6 +158,7 @@ public class WifiManagerTest {
private WifiManager mWifiManager;
private WifiNetworkSuggestion mWifiNetworkSuggestion;
private ScanResultsCallback mScanResultsCallback;
+ private CoexCallback mCoexCallback;
private WifiActivityEnergyInfo mWifiActivityEnergyInfo;
/**
@@ -214,10 +222,149 @@ public class WifiManagerTest {
mRunnable.run();
}
};
+ mCoexCallback = new CoexCallback() {
+ @Override
+ public void onCoexUnsafeChannelsChanged() {
+ mRunnable.run();
+ }
+ };
mWifiActivityEnergyInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0);
}
/**
+ * Check the call to setCoexUnsafeChannels calls WifiServiceImpl to setCoexUnsafeChannels with
+ * the provided CoexUnsafeChannels and restrictions bitmask.
+ */
+ @Test
+ public void testSetCoexUnsafeChannelsGoesToWifiServiceImpl() throws Exception {
+ Set<CoexUnsafeChannel> unsafeChannels = new HashSet<>();
+ int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+ | COEX_RESTRICTION_WIFI_AWARE;
+
+ mWifiManager.setCoexUnsafeChannels(unsafeChannels, restrictions);
+
+ verify(mWifiService).setCoexUnsafeChannels(new ArrayList<>(unsafeChannels), restrictions);
+ }
+
+ /**
+ * Verify an IllegalArgumentException if passed a null value for unsafeChannels.
+ */
+ @Test
+ public void testSetCoexUnsafeChannelsThrowsIllegalArgumentExceptionOnNullUnsafeChannels() {
+ try {
+ mWifiManager.setCoexUnsafeChannels(null, 0);
+ fail("expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Check the call to getCoexUnsafeChannels calls WifiServiceImpl to return the values from
+ * getCoexUnsafeChannels.
+ */
+ @Test
+ public void testGetCoexUnsafeChannelsGoesToWifiServiceImpl() throws Exception {
+ Set<CoexUnsafeChannel> unsafeChannels = new HashSet<>();
+ unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
+ when(mWifiService.getCoexUnsafeChannels()).thenReturn(new ArrayList<>(unsafeChannels));
+
+ assertEquals(mWifiManager.getCoexUnsafeChannels(), unsafeChannels);
+ }
+
+ /**
+ * Verify call to getCoexRestrictions calls WifiServiceImpl to return the value from
+ * getCoexRestrictions.
+ */
+ @Test
+ public void testGetCoexRestrictionsGoesToWifiServiceImpl() throws Exception {
+ int restrictions = COEX_RESTRICTION_WIFI_DIRECT | COEX_RESTRICTION_SOFTAP
+ | COEX_RESTRICTION_WIFI_AWARE;
+ when(mWifiService.getCoexRestrictions()).thenReturn(restrictions);
+
+ assertEquals(mWifiService.getCoexRestrictions(), restrictions);
+ }
+
+
+ /**
+ * Verify an IllegalArgumentException is thrown if callback is not provided.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegisterCoexCallbackWithNullCallback() throws Exception {
+ mWifiManager.registerCoexCallback(mExecutor, null);
+ }
+
+ /**
+ * Verify an IllegalArgumentException is thrown if executor is not provided.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegisterCoexCallbackWithNullExecutor() throws Exception {
+ mWifiManager.registerCoexCallback(null, mCoexCallback);
+ }
+
+ /**
+ * Verify client provided callback is being called to the right callback.
+ */
+ @Test
+ public void testAddCoexCallbackAndReceiveEvent() throws Exception {
+ ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+ mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+ verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+ callbackCaptor.getValue().onCoexUnsafeChannelsChanged();
+ verify(mRunnable).run();
+ }
+
+ /**
+ * Verify client provided callback is being called to the right executor.
+ */
+ @Test
+ public void testRegisterCoexCallbackWithTheTargetExecutor() throws Exception {
+ ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+ mWifiManager.registerCoexCallback(mExecutor, mCoexCallback);
+ verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+ mWifiManager.registerCoexCallback(mAnotherExecutor, mCoexCallback);
+ callbackCaptor.getValue().onCoexUnsafeChannelsChanged();
+ verify(mExecutor, never()).execute(any(Runnable.class));
+ verify(mAnotherExecutor).execute(any(Runnable.class));
+ }
+
+ /**
+ * Verify client register unregister then register again, to ensure callback still works.
+ */
+ @Test
+ public void testRegisterUnregisterThenRegisterAgainWithCoexCallback() throws Exception {
+ ArgumentCaptor<ICoexCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ICoexCallback.Stub.class);
+ mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+ verify(mWifiService).registerCoexCallback(callbackCaptor.capture());
+ mWifiManager.unregisterCoexCallback(mCoexCallback);
+ callbackCaptor.getValue().onCoexUnsafeChannelsChanged();
+ verify(mRunnable, never()).run();
+ mWifiManager.registerCoexCallback(new SynchronousExecutor(), mCoexCallback);
+ callbackCaptor.getValue().onCoexUnsafeChannelsChanged();
+ verify(mRunnable).run();
+ }
+
+ /**
+ * Verify client unregisterCoexCallback.
+ */
+ @Test
+ public void testUnregisterCoexCallback() throws Exception {
+ mWifiManager.unregisterCoexCallback(mCoexCallback);
+ verify(mWifiService).unregisterCoexCallback(any());
+ }
+
+ /**
+ * Verify client unregisterCoexCallback with null callback will cause an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnregisterCoexCallbackWithNullCallback() throws Exception {
+ mWifiManager.unregisterCoexCallback(null);
+ }
+
+
+ /**
* Check the call to startSoftAp calls WifiService to startSoftAp with the provided
* WifiConfiguration. Verify that the return value is propagated to the caller.
*/