diff --git a/api/current.txt b/api/current.txt index 231c996437de8..d512494d8cbe1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -30010,6 +30010,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int addNetworkSuggestions(@NonNull java.util.List); method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void addScanResultsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.ScanResultsListener); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public void addSuggestionConnectionStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener); method public static int calculateSignalLevel(int, int); method @Deprecated public void cancelWps(android.net.wifi.WifiManager.WpsCallback); method public static int compareSignalLevel(int, int); @@ -30046,6 +30047,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List); method @Deprecated @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", "android.permission.NETWORK_CARRIER_PROVISIONING"}) public void removePasspointConfiguration(String); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeScanResultsListener(@NonNull android.net.wifi.WifiManager.ScanResultsListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeSuggestionConnectionStatusListener(@NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener); method @Deprecated public boolean saveConfiguration(); method public void setTdlsEnabled(java.net.InetAddress, boolean); method public void setTdlsEnabledWithMacAddress(String, boolean); @@ -30079,6 +30081,10 @@ package android.net.wifi { field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL = 1; // 0x1 field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5; // 0x5 field public static final int STATUS_NETWORK_SUGGESTIONS_SUCCESS = 0; // 0x0 + field public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_ASSOCIATION = 1; // 0x1 + field public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION = 2; // 0x2 + field public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING = 3; // 0x3 + field public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_UNKNOWN = 0; // 0x0 field @Deprecated public static final String SUPPLICANT_CONNECTION_CHANGE_ACTION = "android.net.wifi.supplicant.CONNECTION_CHANGE"; field @Deprecated public static final String SUPPLICANT_STATE_CHANGED_ACTION = "android.net.wifi.supplicant.STATE_CHANGE"; field @Deprecated public static final int WIFI_MODE_FULL = 1; // 0x1 @@ -30125,6 +30131,10 @@ package android.net.wifi { method public void onScanResultsAvailable(); } + public static interface WifiManager.SuggestionConnectionStatusListener { + method public void onConnectionStatus(@NonNull android.net.wifi.WifiNetworkSuggestion, int); + } + public class WifiManager.WifiLock { method public void acquire(); method public boolean isHeld(); diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt index 2ca8cf4ae0a47..4a37e67c48b00 100644 --- a/api/lint-baseline.txt +++ b/api/lint-baseline.txt @@ -534,7 +534,7 @@ MissingNullability: android.icu.util.VersionInfo#UNICODE_12_1: MissingNullability: android.media.MediaMetadataRetriever#getFrameAtTime(long, int, android.media.MediaMetadataRetriever.BitmapParams): MissingNullability: android.media.MediaMetadataRetriever#getScaledFrameAtTime(long, int, int, int, android.media.MediaMetadataRetriever.BitmapParams): - + RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback, android.os.Handler): @@ -1160,7 +1160,7 @@ SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(jav SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener): SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener): - + StreamFiles: android.content.res.loader.DirectoryResourceLoader#DirectoryResourceLoader(java.io.File): diff --git a/wifi/java/android/net/wifi/ISuggestionConnectionStatusListener.aidl b/wifi/java/android/net/wifi/ISuggestionConnectionStatusListener.aidl new file mode 100644 index 0000000000000..b49e49ba0cd54 --- /dev/null +++ b/wifi/java/android/net/wifi/ISuggestionConnectionStatusListener.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 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.net.wifi.WifiNetworkSuggestion; + +/** + * Interface for suggestion network connection listener. + * + * @hide + */ +oneway interface ISuggestionConnectionStatusListener +{ + void onConnectionStatus(in WifiNetworkSuggestion wifiNetworkSuggestion, int failureReason); +} diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 023df70ef55a3..bc73a93f1e567 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -30,6 +30,7 @@ import android.net.wifi.ILocalOnlyHotspotCallback; import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.IScanResultsListener; import android.net.wifi.ISoftApCallback; +import android.net.wifi.ISuggestionConnectionStatusListener; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.ITxPacketCountListener; import android.net.wifi.IOnWifiUsabilityStatsListener; @@ -236,4 +237,8 @@ interface IWifiManager void registerScanResultsListener(in IBinder binder, in IScanResultsListener Listener, int listenerIdentifier); void unregisterScanResultsListener(int listenerIdentifier); + + void registerSuggestionConnectionStatusListener(in IBinder binder, in ISuggestionConnectionStatusListener listener, int listenerIdentifier, String packageName); + + void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 8108fef5d3ee0..24d099af6f083 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -207,6 +207,33 @@ public class WifiManager { @Retention(RetentionPolicy.SOURCE) public @interface NetworkSuggestionsStatusCode {} + /** + * Reason code if suggested network connection attempt failed with an unknown failure. + */ + public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_UNKNOWN = 0; + /** + * Reason code if suggested network connection attempt failed with association failure. + */ + public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_ASSOCIATION = 1; + /** + * Reason code if suggested network connection attempt failed with an authentication failure. + */ + public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION = 2; + /** + * Reason code if suggested network connection attempt failed with an IP provision failure. + */ + public static final int STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING = 3; + + /** @hide */ + @IntDef(prefix = {"STATUS_SUGGESTION_CONNECTION_FAILURE_"}, + value = {STATUS_SUGGESTION_CONNECTION_FAILURE_UNKNOWN, + STATUS_SUGGESTION_CONNECTION_FAILURE_ASSOCIATION, + STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION, + STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SuggestionConnectionStatusCode {} + /** * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently * @hide @@ -5229,7 +5256,7 @@ public class WifiManager { } /** - * Base class for scan results listener. Should be implemented by applications and set when + * Interface for scan results listener. Should be implemented by applications and set when * calling {@link WifiManager#addScanResultsListener(Executor, ScanResultsListener)}. */ public interface ScanResultsListener { @@ -5315,4 +5342,108 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** + * Interface for suggestion connection status listener. + * Should be implemented by applications and set when calling + * {@link WifiManager#addSuggestionConnectionStatusListener( + * Executor, SuggestionConnectionStatusListener)}. + */ + public interface SuggestionConnectionStatusListener { + + /** + * Called when the framework attempted to connect to a suggestion provided by the + * registering app, but the connection to the suggestion failed. + * @param wifiNetworkSuggestion The suggestion which failed to connect. + * @param failureReason the connection failure reason code. One of + * {@link #STATUS_SUGGESTION_CONNECTION_FAILURE_ASSOCIATION}, + * {@link #STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION}, + * {@link #STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING} + * {@link #STATUS_SUGGESTION_CONNECTION_FAILURE_UNKNOWN} + */ + void onConnectionStatus( + @NonNull WifiNetworkSuggestion wifiNetworkSuggestion, + @SuggestionConnectionStatusCode int failureReason); + } + + private class SuggestionConnectionStatusListenerProxy extends + ISuggestionConnectionStatusListener.Stub { + private final Executor mExecutor; + private final SuggestionConnectionStatusListener mListener; + + SuggestionConnectionStatusListenerProxy(@NonNull Executor executor, + @NonNull SuggestionConnectionStatusListener listener) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onConnectionStatus(@NonNull WifiNetworkSuggestion wifiNetworkSuggestion, + int failureReason) { + mExecutor.execute(() -> + mListener.onConnectionStatus(wifiNetworkSuggestion, failureReason)); + } + + } + + /** + * Add a listener for suggestion networks. See {@link SuggestionConnectionStatusListener}. + * Caller will receive the event when suggested network have connection failure. + * Caller can remove a previously registered listener using + * {@link WifiManager#removeSuggestionConnectionStatusListener( + * SuggestionConnectionStatusListener)} + * Same caller can add multiple listeners to monitor the event. + *

+ * Applications should have the + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and + * {@link android.Manifest.permission#ACCESS_WIFI_STATE} permissions. + * Callers without the permission will trigger a {@link java.lang.SecurityException}. + *

+ * + * @param executor The executor to execute the listener of the {@code listener} object. + * @param listener listener for suggestion network connection failure. + */ + @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE}) + public void addSuggestionConnectionStatusListener(@NonNull @CallbackExecutor Executor executor, + @NonNull SuggestionConnectionStatusListener listener) { + if (listener == null) throw new IllegalArgumentException("Listener cannot be null"); + if (executor == null) throw new IllegalArgumentException("Executor cannot be null"); + Log.v(TAG, "addSuggestionConnectionStatusListener listener=" + listener + + ", executor=" + executor); + try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.registerSuggestionConnectionStatusListener(new Binder(), + new SuggestionConnectionStatusListenerProxy(executor, listener), + listener.hashCode(), mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + + /** + * Allow callers to remove a previously registered listener. After calling this method, + * applications will no longer receive suggestion connection events through that listener. + * + * @param listener listener to remove. + */ + @RequiresPermission(ACCESS_WIFI_STATE) + public void removeSuggestionConnectionStatusListener( + @NonNull SuggestionConnectionStatusListener listener) { + if (listener == null) throw new IllegalArgumentException("Listener cannot be null"); + Log.v(TAG, "removeSuggestionConnectionStatusListener: listener=" + listener); + try { + IWifiManager iWifiManager = getIWifiManager(); + if (iWifiManager == null) { + throw new RemoteException("Wifi service is not running"); + } + iWifiManager.unregisterSuggestionConnectionStatusListener(listener.hashCode(), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index 4b7d205e7922f..671708ff4c9db 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -28,6 +28,7 @@ import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.IOnWifiUsabilityStatsListener; import android.net.wifi.IScanResultsListener; import android.net.wifi.ISoftApCallback; +import android.net.wifi.ISuggestionConnectionStatusListener; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.ITxPacketCountListener; import android.net.wifi.IWifiManager; @@ -540,4 +541,17 @@ public class BaseWifiService extends IWifiManager.Stub { public void unregisterScanResultsListener(int listenerIdentifier) { throw new UnsupportedOperationException(); } + + @Override + public void registerSuggestionConnectionStatusListener(IBinder binder, + ISuggestionConnectionStatusListener listener, + int listenerIdentifier, String packageName) { + throw new UnsupportedOperationException(); + } + + @Override + public void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, + String packageName) { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 14e994cf0f085..a78cca3aefdd1 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -61,6 +61,7 @@ import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback; import android.net.wifi.WifiManager.OnWifiUsabilityStatsListener; import android.net.wifi.WifiManager.ScanResultsListener; import android.net.wifi.WifiManager.SoftApCallback; +import android.net.wifi.WifiManager.SuggestionConnectionStatusListener; import android.net.wifi.WifiManager.TrafficStateCallback; import android.os.Binder; import android.os.Build; @@ -109,12 +110,14 @@ public class WifiManagerTest { @Mock NetworkRequestMatchCallback mNetworkRequestMatchCallback; @Mock OnWifiUsabilityStatsListener mOnWifiUsabilityStatsListener; @Mock ScanResultsListener mScanResultListener; + @Mock SuggestionConnectionStatusListener mListener; @Mock Executor mCallbackExecutor; @Mock Executor mExecutor; private Handler mHandler; private TestLooper mLooper; private WifiManager mWifiManager; + private WifiNetworkSuggestion mWifiNetworkSuggestion; @Before public void setUp() throws Exception { @@ -126,6 +129,7 @@ public class WifiManagerTest { when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME); mWifiManager = new WifiManager(mContext, mWifiService, mLooper.getLooper()); verify(mWifiService).getVerboseLoggingLevel(); + mWifiNetworkSuggestion = new WifiNetworkSuggestion(); } /** @@ -1803,4 +1807,69 @@ public class WifiManagerTest { public void testRemoveScanResultsListenerWithNullListener() throws Exception { mWifiManager.removeScanResultsListener(null); } + + /** + * Verify an IllegalArgumentException is thrown if executor not provided. + */ + @Test(expected = IllegalArgumentException.class) + public void testAddSuggestionConnectionStatusListenerWithNullExecutor() { + mWifiManager.addSuggestionConnectionStatusListener(null, mListener); + } + + /** + * Verify an IllegalArgumentException is thrown if listener is not provided. + */ + @Test(expected = IllegalArgumentException.class) + public void testAddSuggestionConnectionStatusListenerWithNullListener() { + mWifiManager.addSuggestionConnectionStatusListener(mExecutor, null); + } + + /** + * Verify client provided listener is being called to the right listener. + */ + @Test + public void testAddSuggestionConnectionStatusListenerAndReceiveEvent() throws Exception { + int errorCode = WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(ISuggestionConnectionStatusListener.Stub.class); + Executor executor = new SynchronousExecutor(); + mWifiManager.addSuggestionConnectionStatusListener(executor, mListener); + verify(mWifiService).registerSuggestionConnectionStatusListener(any(IBinder.class), + callbackCaptor.capture(), anyInt(), anyString()); + callbackCaptor.getValue().onConnectionStatus(mWifiNetworkSuggestion, errorCode); + verify(mListener).onConnectionStatus(any(WifiNetworkSuggestion.class), eq(errorCode)); + } + + /** + * Verify client provided listener is being called to the right executor. + */ + @Test + public void testAddSuggestionConnectionStatusListenerWithTheTargetExecutor() throws Exception { + int errorCode = WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION; + ArgumentCaptor callbackCaptor = + ArgumentCaptor.forClass(ISuggestionConnectionStatusListener.Stub.class); + mWifiManager.addSuggestionConnectionStatusListener(mExecutor, mListener); + verify(mWifiService).registerSuggestionConnectionStatusListener(any(IBinder.class), + callbackCaptor.capture(), anyInt(), anyString()); + callbackCaptor.getValue().onConnectionStatus(any(WifiNetworkSuggestion.class), errorCode); + verify(mExecutor).execute(any(Runnable.class)); + } + + /** + * Verify an IllegalArgumentException is thrown if listener is not provided. + */ + @Test(expected = IllegalArgumentException.class) + public void testRemoveSuggestionConnectionListenerWithNullListener() { + mWifiManager.removeSuggestionConnectionStatusListener(null); + } + + /** + * Verify removeSuggestionConnectionListener. + */ + @Test + public void testRemoveSuggestionConnectionListener() throws Exception { + + mWifiManager.removeSuggestionConnectionStatusListener(mListener); + verify(mWifiService).unregisterSuggestionConnectionStatusListener(anyInt(), anyString()); + } }