diff options
| author | 2020-11-10 12:04:37 -0800 | |
|---|---|---|
| committer | 2020-11-17 13:49:41 -0800 | |
| commit | 1459f65a7ce327fa7e7301e194cb5089afbde801 (patch) | |
| tree | 921d08d9185b9b98b706f3d468ce32d1d6336921 | |
| parent | a3bac99dc42f10944c47411de9867218dbec6618 (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.txt | 2 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 10 | ||||
| -rw-r--r-- | wifi/aidl-export/android/net/wifi/CoexUnsafeChannel.aidl | 19 | ||||
| -rw-r--r-- | wifi/api/system-current.txt | 24 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/CoexUnsafeChannel.java | 176 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/ICoexCallback.aidl | 26 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/IWifiManager.aidl | 12 | ||||
| -rw-r--r-- | wifi/java/android/net/wifi/WifiManager.java | 234 | ||||
| -rw-r--r-- | wifi/tests/src/android/net/wifi/CoexUnsafeChannelTest.java | 97 | ||||
| -rw-r--r-- | wifi/tests/src/android/net/wifi/WifiManagerTest.java | 147 |
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. */ |