diff options
author | 2023-01-12 11:24:30 -0800 | |
---|---|---|
committer | 2023-01-18 06:08:43 +0000 | |
commit | ee93b25ab0557a0e4bc09a760ca3ea096240de07 (patch) | |
tree | 1e2e20a7d08c6f1ad9f9afdcd20ea59367706626 | |
parent | fe723e662e14a59b0613343d6ed0798a71e8e82e (diff) |
Add listener for Local-only connection
Bug: 265351440
Test: atest android.net.wifi, com.android.server.wifi
API-Coverage-Bug: 265880319
Change-Id: I2723e2619c57ac8c4fc3c78ee5639bb3db2b565f
12 files changed, 476 insertions, 22 deletions
diff --git a/framework/aidl-export/android/net/wifi/WifiNetworkSpecifier.aidl b/framework/aidl-export/android/net/wifi/WifiNetworkSpecifier.aidl new file mode 100644 index 0000000000..4933a9af4f --- /dev/null +++ b/framework/aidl-export/android/net/wifi/WifiNetworkSpecifier.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +parcelable WifiNetworkSpecifier; diff --git a/framework/api/current.txt b/framework/api/current.txt index 76e91a9d92..a48840fe3d 100644 --- a/framework/api/current.txt +++ b/framework/api/current.txt @@ -418,6 +418,7 @@ package android.net.wifi { } public class WifiManager { + method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void addLocalOnlyConnectionFailureListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.LocalOnlyConnectionFailureListener); method @Deprecated public int addNetwork(android.net.wifi.WifiConfiguration); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING}, conditional=true) public android.net.wifi.WifiManager.AddNetworkResult addNetworkPrivileged(@NonNull android.net.wifi.WifiConfiguration); method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int addNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>); @@ -495,6 +496,7 @@ package android.net.wifi { method @Deprecated public boolean reconnect(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void registerScanResultsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.ScanResultsCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void registerSubsystemRestartTrackingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SubsystemRestartTrackingCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeLocalOnlyConnectionFailureListener(@NonNull android.net.wifi.WifiManager.LocalOnlyConnectionFailureListener); method @Deprecated public boolean removeNetwork(int); method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>); method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>, int); @@ -539,6 +541,12 @@ package android.net.wifi { field public static final String NETWORK_STATE_CHANGED_ACTION = "android.net.wifi.STATE_CHANGE"; field public static final String RSSI_CHANGED_ACTION = "android.net.wifi.RSSI_CHANGED"; field public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS"; + field public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_ASSOCIATION = 1; // 0x1 + field public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_AUTHENTICATION = 2; // 0x2 + field public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_IP_PROVISIONING = 3; // 0x3 + field public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NOT_FOUND = 4; // 0x4 + field public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NO_RESPONSE = 5; // 0x5 + field public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_UNKNOWN = 0; // 0x0 field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 3; // 0x3 field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 4; // 0x4 field public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID = 7; // 0x7 @@ -610,6 +618,10 @@ package android.net.wifi { method @NonNull public java.util.Set<java.lang.String> getPackages(); } + public static interface WifiManager.LocalOnlyConnectionFailureListener { + method public void onConnectionStatus(@NonNull android.net.wifi.WifiNetworkSpecifier, int); + } + public static class WifiManager.LocalOnlyHotspotCallback { ctor public WifiManager.LocalOnlyHotspotCallback(); method public void onFailed(int); diff --git a/framework/java/android/net/wifi/BaseWifiService.java b/framework/java/android/net/wifi/BaseWifiService.java index f21041605b..6abbea0fee 100644 --- a/framework/java/android/net/wifi/BaseWifiService.java +++ b/framework/java/android/net/wifi/BaseWifiService.java @@ -969,4 +969,16 @@ public class BaseWifiService extends IWifiManager.Stub { public void removeAllQosPolicies() { throw new UnsupportedOperationException(); } + + @Override + public void addLocalOnlyConnectionStatusListener(ILocalOnlyConnectionStatusListener listener, + String packageName, String featureId) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeLocalOnlyConnectionStatusListener(ILocalOnlyConnectionStatusListener listener, + String packageName) { + throw new UnsupportedOperationException(); + } } diff --git a/framework/java/android/net/wifi/ILocalOnlyConnectionStatusListener.aidl b/framework/java/android/net/wifi/ILocalOnlyConnectionStatusListener.aidl new file mode 100644 index 0000000000..824ab3eef5 --- /dev/null +++ b/framework/java/android/net/wifi/ILocalOnlyConnectionStatusListener.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.net.wifi.WifiNetworkSpecifier; + +/** + * Interface for local-only network connection listener. + * + * @hide + */ +oneway interface ILocalOnlyConnectionStatusListener +{ + void onConnectionStatus(in WifiNetworkSpecifier wifiNetworkSpecifier, int failureReason); +} diff --git a/framework/java/android/net/wifi/IWifiManager.aidl b/framework/java/android/net/wifi/IWifiManager.aidl index a75e4863e7..85ae182270 100644 --- a/framework/java/android/net/wifi/IWifiManager.aidl +++ b/framework/java/android/net/wifi/IWifiManager.aidl @@ -32,6 +32,7 @@ import android.net.wifi.IInterfaceCreationInfoCallback; import android.net.wifi.ILastCallerListener; import android.net.wifi.IListListener; import android.net.wifi.ILocalOnlyHotspotCallback; +import android.net.wifi.ILocalOnlyConnectionStatusListener; import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.IOnWifiActivityEnergyInfoListener; import android.net.wifi.IOnWifiDriverCountryCodeChangedListener; @@ -337,6 +338,10 @@ interface IWifiManager void unregisterSuggestionConnectionStatusListener(in ISuggestionConnectionStatusListener listener, String packageName); + void addLocalOnlyConnectionStatusListener(in ILocalOnlyConnectionStatusListener listener, String packageName, String featureId); + + void removeLocalOnlyConnectionStatusListener(in ILocalOnlyConnectionStatusListener listener, String packageName); + int calculateSignalLevel(int rssi); List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults); diff --git a/framework/java/android/net/wifi/WifiManager.java b/framework/java/android/net/wifi/WifiManager.java index 39a7d5f27e..13c7194aa1 100644 --- a/framework/java/android/net/wifi/WifiManager.java +++ b/framework/java/android/net/wifi/WifiManager.java @@ -308,6 +308,43 @@ public class WifiManager { public @interface SuggestionConnectionStatusCode {} /** + * Reason code if local-only network connection attempt failed with an unknown failure. + */ + public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_UNKNOWN = 0; + /** + * Reason code if local-only network connection attempt failed with association failure. + */ + public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_ASSOCIATION = 1; + /** + * Reason code if local-only network connection attempt failed with an authentication failure. + */ + public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_AUTHENTICATION = 2; + /** + * Reason code if local-only network connection attempt failed with an IP provisioning failure. + */ + public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_IP_PROVISIONING = 3; + /** + * Reason code if local-only network connection attempt failed with AP not in range. + */ + public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NOT_FOUND = 4; + /** + * Reason code if local-only network connection attempt failed with AP not responding + */ + public static final int STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NO_RESPONSE = 5; + + /** @hide */ + @IntDef(prefix = {"STATUS_LOCAL_ONLY_CONNECTION_FAILURE_"}, + value = {STATUS_LOCAL_ONLY_CONNECTION_FAILURE_UNKNOWN, + STATUS_LOCAL_ONLY_CONNECTION_FAILURE_ASSOCIATION, + STATUS_LOCAL_ONLY_CONNECTION_FAILURE_AUTHENTICATION, + STATUS_LOCAL_ONLY_CONNECTION_FAILURE_IP_PROVISIONING, + STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NOT_FOUND, + STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NO_RESPONSE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LocalOnlyConnectionStatusCode {} + + /** * Status code if suggestion approval status is unknown, an App which hasn't made any * suggestions will get this code. */ @@ -1906,6 +1943,8 @@ public class WifiManager { sActiveCountryCodeChangedCallbackMap = new SparseArray(); private static final SparseArray<ISoftApCallback> sLocalOnlyHotspotSoftApCallbackMap = new SparseArray(); + private static final SparseArray<ILocalOnlyConnectionStatusListener> + sLocalOnlyConnectionStatusListenerMap = new SparseArray(); /** * Create a new WifiManager instance. @@ -8704,11 +8743,7 @@ public class WifiManager { * 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} + * @param failureReason the connection failure reason code. */ void onConnectionStatus( @NonNull WifiNetworkSuggestion wifiNetworkSuggestion, @@ -8729,6 +8764,7 @@ public class WifiManager { @Override public void onConnectionStatus(@NonNull WifiNetworkSuggestion wifiNetworkSuggestion, int failureReason) { + Binder.clearCallingIdentity(); mExecutor.execute(() -> mListener.onConnectionStatus(wifiNetworkSuggestion, failureReason)); } @@ -8736,6 +8772,45 @@ public class WifiManager { } /** + * Interface for local-only connection failure listener. + * Should be implemented by applications and set when calling + * {@link WifiManager#addLocalOnlyConnectionFailureListener(Executor, LocalOnlyConnectionFailureListener)} + */ + public interface LocalOnlyConnectionFailureListener { + + /** + * Called when the framework attempted to connect to a local-only network requested by the + * registering app, but the connection to the network failed. + * @param wifiNetworkSpecifier The {@link WifiNetworkSpecifier} which failed to connect. + * @param failureReason the connection failure reason code. + */ + void onConnectionStatus( + @NonNull WifiNetworkSpecifier wifiNetworkSpecifier, + @LocalOnlyConnectionStatusCode int failureReason); + } + + private static class LocalOnlyConnectionStatusListenerProxy extends + ILocalOnlyConnectionStatusListener.Stub { + private final Executor mExecutor; + private final LocalOnlyConnectionFailureListener mListener; + + LocalOnlyConnectionStatusListenerProxy(@NonNull Executor executor, + @NonNull LocalOnlyConnectionFailureListener listener) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onConnectionStatus(@NonNull WifiNetworkSpecifier networkSpecifier, + int failureReason) { + Binder.clearCallingIdentity(); + mExecutor.execute(() -> + mListener.onConnectionStatus(networkSpecifier, failureReason)); + } + + } + + /** * Add a listener listening to wifi verbose logging changes. * See {@link WifiVerboseLoggingStatusChangedListener}. * Caller can remove a previously registered listener using @@ -8888,6 +8963,72 @@ public class WifiManager { } /** + * Add a listener for local-only networks. See {@link WifiNetworkSpecifier}. + * Specify the caller will only get connection failures for networks they requested. + * Caller can remove a previously registered listener using + * {@link WifiManager#removeLocalOnlyConnectionFailureListener(LocalOnlyConnectionFailureListener)} + * Same caller can add multiple listeners to monitor the event. + * <p> + * Applications should have the {@link android.Manifest.permission#ACCESS_WIFI_STATE} + * permissions. + * Callers without the permission will trigger a {@link java.lang.SecurityException}. + * <p> + * + * @param executor The executor to execute the listener of the {@code listener} object. + * @param listener listener for local-only network connection failure. + */ + @RequiresPermission(ACCESS_WIFI_STATE) + public void addLocalOnlyConnectionFailureListener(@NonNull @CallbackExecutor Executor executor, + @NonNull LocalOnlyConnectionFailureListener listener) { + if (listener == null) throw new IllegalArgumentException("Listener cannot be null"); + if (executor == null) throw new IllegalArgumentException("Executor cannot be null"); + try { + synchronized (sLocalOnlyConnectionStatusListenerMap) { + if (sLocalOnlyConnectionStatusListenerMap + .contains(System.identityHashCode(listener))) { + Log.w(TAG, "Same listener already registered"); + return; + } + ILocalOnlyConnectionStatusListener.Stub binderCallback = + new LocalOnlyConnectionStatusListenerProxy(executor, listener); + sLocalOnlyConnectionStatusListenerMap.put(System.identityHashCode(listener), + binderCallback); + mService.addLocalOnlyConnectionStatusListener(binderCallback, + mContext.getOpPackageName(), mContext.getAttributionTag()); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Allow callers to remove a previously registered listener. After calling this method, + * applications will no longer receive local-only connection events through that listener. + * + * @param listener listener to remove. + */ + @RequiresPermission(ACCESS_WIFI_STATE) + public void removeLocalOnlyConnectionFailureListener( + @NonNull LocalOnlyConnectionFailureListener listener) { + if (listener == null) throw new IllegalArgumentException("Listener cannot be null"); + try { + synchronized (sLocalOnlyConnectionStatusListenerMap) { + int listenerIdentifier = System.identityHashCode(listener); + if (!sLocalOnlyConnectionStatusListenerMap.contains(listenerIdentifier)) { + Log.w(TAG, "Unknown external callback " + listenerIdentifier); + return; + } + mService.removeLocalOnlyConnectionStatusListener( + sLocalOnlyConnectionStatusListenerMap.get(listenerIdentifier), + mContext.getOpPackageName()); + sLocalOnlyConnectionStatusListenerMap.remove(listenerIdentifier); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Parse the list of channels the DPP enrollee reports when it fails to find an AP. * * @param channelList List of channels in the format defined in the DPP specification. diff --git a/framework/tests/src/android/net/wifi/WifiManagerTest.java b/framework/tests/src/android/net/wifi/WifiManagerTest.java index 3c6aa139f4..9190f0b813 100644 --- a/framework/tests/src/android/net/wifi/WifiManagerTest.java +++ b/framework/tests/src/android/net/wifi/WifiManagerTest.java @@ -186,6 +186,8 @@ public class WifiManagerTest { @Mock OnWifiUsabilityStatsListener mOnWifiUsabilityStatsListener; @Mock OnWifiActivityEnergyInfoListener mOnWifiActivityEnergyInfoListener; @Mock SuggestionConnectionStatusListener mSuggestionConnectionListener; + @Mock + WifiManager.LocalOnlyConnectionFailureListener mLocalOnlyConnectionFailureListener; @Mock Runnable mRunnable; @Mock Executor mExecutor; @Mock Executor mAnotherExecutor; @@ -3117,7 +3119,8 @@ public class WifiManagerTest { ArgumentCaptor<ISuggestionConnectionStatusListener.Stub> callbackCaptor = ArgumentCaptor.forClass(ISuggestionConnectionStatusListener.Stub.class); Executor executor = new SynchronousExecutor(); - mWifiManager.addSuggestionConnectionStatusListener(executor, mSuggestionConnectionListener); + mWifiManager.addSuggestionConnectionStatusListener(executor, + mSuggestionConnectionListener); verify(mWifiService).registerSuggestionConnectionStatusListener(callbackCaptor.capture(), anyString(), nullable(String.class)); callbackCaptor.getValue().onConnectionStatus(mWifiNetworkSuggestion, errorCode); @@ -3983,4 +3986,19 @@ public class WifiManagerTest { mWifiManager.removeQosPolicy(policyId); verify(mWifiService).removeQosPolicy(eq(policyId)); } + + @Test + public void testAddRemoveLocaOnlyConnectionListener() throws RemoteException { + assertThrows(IllegalArgumentException.class, () -> mWifiManager + .addLocalOnlyConnectionFailureListener(null, mLocalOnlyConnectionFailureListener)); + assertThrows(IllegalArgumentException.class, () -> mWifiManager + .addLocalOnlyConnectionFailureListener(mExecutor, null)); + mWifiManager.addLocalOnlyConnectionFailureListener(mExecutor, + mLocalOnlyConnectionFailureListener); + verify(mWifiService).addLocalOnlyConnectionStatusListener(any(), eq(TEST_PACKAGE_NAME), + nullable(String.class)); + mWifiManager.removeLocalOnlyConnectionFailureListener(mLocalOnlyConnectionFailureListener); + verify(mWifiService).removeLocalOnlyConnectionStatusListener(any(), eq(TEST_PACKAGE_NAME)); + } + } diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java index 3d650e6f0f..9fcd107997 100644 --- a/service/java/com/android/server/wifi/WifiNetworkFactory.java +++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java @@ -44,6 +44,7 @@ import android.net.NetworkFactory; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.wifi.IActionListener; +import android.net.wifi.ILocalOnlyConnectionStatusListener; import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.INetworkRequestUserSelectionCallback; import android.net.wifi.ScanResult; @@ -51,6 +52,7 @@ import android.net.wifi.SecurityParams; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.SecurityType; import android.net.wifi.WifiContext; +import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiScanner; import android.net.wifi.util.ScanResultUtil; @@ -195,6 +197,10 @@ public class WifiNetworkFactory extends NetworkFactory { */ private boolean mHasNewDataToSerialize = false; + private final HashMap<String, RemoteCallbackList<ILocalOnlyConnectionStatusListener>> + mLocalOnlyStatusListenerPerApp = new HashMap<>(); + private final HashMap<String, String> mFeatureIdPerApp = new HashMap<>(); + /** * Helper class to store an access point that the user previously approved for a specific app. * TODO(b/123014687): Move to a common util class. @@ -308,7 +314,8 @@ public class WifiNetworkFactory extends NetworkFactory { public void onAlarm() { Log.e(TAG, "Timed-out connecting to network"); if (mUserSelectedNetwork != null) { - handleNetworkConnectionFailure(mUserSelectedNetwork, mUserSelectedNetwork.BSSID); + handleNetworkConnectionFailure(mUserSelectedNetwork, mUserSelectedNetwork.BSSID, + WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT); } else { Log.wtf(TAG, "mUserSelectedNetwork is null, when connection time out"); } @@ -364,7 +371,8 @@ public class WifiNetworkFactory extends NetworkFactory { Log.e(TAG, "mUserSelectedNetwork is null, when connection failure"); return; } - handleNetworkConnectionFailure(mUserSelectedNetwork, mUserSelectedNetwork.BSSID); + handleNetworkConnectionFailure(mUserSelectedNetwork, mUserSelectedNetwork.BSSID, + reason); } } @@ -1204,7 +1212,7 @@ public class WifiNetworkFactory extends NetworkFactory { if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) { handleNetworkConnectionSuccess(network, bssid); } else { - handleNetworkConnectionFailure(network, bssid); + handleNetworkConnectionFailure(network, bssid, failureCode); } } @@ -1232,7 +1240,7 @@ public class WifiNetworkFactory extends NetworkFactory { * Invoked by {@link ClientModeImpl} on failure to connect to a network. */ private void handleNetworkConnectionFailure(@NonNull WifiConfiguration failedNetwork, - @NonNull String failedBssid) { + @NonNull String failedBssid, int failureCode) { if (mUserSelectedNetwork == null || failedNetwork == null) { return; } @@ -1270,6 +1278,9 @@ public class WifiNetworkFactory extends NetworkFactory { } mRegisteredCallbacks.finishBroadcast(); } + sendConnectionFailureIfAllowed(mActiveSpecificNetworkRequest.getRequestorPackageName(), + mActiveSpecificNetworkRequest.getRequestorUid(), + mActiveSpecificNetworkRequestSpecifier, failureCode); teardownForActiveRequest(); } @@ -2012,21 +2023,108 @@ public class WifiNetworkFactory extends NetworkFactory { } /** - * Remove all user approved access points for the specified app. + * Remove all user approved access points and listener for the specified app. */ - public void removeUserApprovedAccessPointsForApp(@NonNull String packageName) { + public void removeApp(@NonNull String packageName) { if (mUserApprovedAccessPointMap.remove(packageName) != null) { Log.i(TAG, "Removing all approved access points for " + packageName); } + RemoteCallbackList<ILocalOnlyConnectionStatusListener> listenerTracker = + mLocalOnlyStatusListenerPerApp.remove(packageName); + if (listenerTracker != null) listenerTracker.kill(); + mFeatureIdPerApp.remove(packageName); saveToStore(); } /** + * Add a listener to get the connection failure of the local-only conncetion + */ + public void addLocalOnlyConnectionStatusListener( + @NonNull ILocalOnlyConnectionStatusListener listener, String packageName, + String featureId) { + RemoteCallbackList<ILocalOnlyConnectionStatusListener> listenersTracker = + mLocalOnlyStatusListenerPerApp.get(packageName); + if (listenersTracker == null) { + listenersTracker = new RemoteCallbackList<>(); + } + listenersTracker.register(listener); + mLocalOnlyStatusListenerPerApp.put(packageName, listenersTracker); + if (!mFeatureIdPerApp.containsKey(packageName)) { + mFeatureIdPerApp.put(packageName, featureId); + } + } + + /** + * Remove a listener which added before + */ + public void removeLocalOnlyConnectionStatusListener( + @NonNull ILocalOnlyConnectionStatusListener listener, String packageName) { + RemoteCallbackList<ILocalOnlyConnectionStatusListener> listenersTracker = + mLocalOnlyStatusListenerPerApp.get(packageName); + if (listenersTracker == null || !listenersTracker.unregister(listener)) { + Log.w(TAG, "removeLocalOnlyConnectionFailureListener: Listener from " + packageName + + " already unregister."); + } + if (listenersTracker != null && listenersTracker.getRegisteredCallbackCount() == 0) { + mLocalOnlyStatusListenerPerApp.remove(packageName); + mFeatureIdPerApp.remove(packageName); + } + } + + private void sendConnectionFailureIfAllowed(String packageName, + int uid, @NonNull WifiNetworkSpecifier networkSpecifier, int connectionEvent) { + RemoteCallbackList<ILocalOnlyConnectionStatusListener> listenersTracker = + mLocalOnlyStatusListenerPerApp.get(packageName); + if (listenersTracker == null || listenersTracker.getRegisteredCallbackCount() == 0) { + return; + } + + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Sending connection failure event to " + packageName); + } + for (int i = 0; i < listenersTracker.beginBroadcast(); i++) { + try { + listenersTracker.getBroadcastItem(i).onConnectionStatus(networkSpecifier, + internalConnectionEventToLocalOnlyFailureCode(connectionEvent)); + } catch (RemoteException e) { + Log.e(TAG, "sendNetworkCallback: remote exception -- " + e); + } + } + listenersTracker.finishBroadcast(); + } + + private @WifiManager.LocalOnlyConnectionStatusCode int + internalConnectionEventToLocalOnlyFailureCode(int connectionEvent) { + switch (connectionEvent) { + case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION: + case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT: + return WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_ASSOCIATION; + case WifiMetrics.ConnectionEvent.FAILURE_SSID_TEMP_DISABLED: + case WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE: + return WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_AUTHENTICATION; + case WifiMetrics.ConnectionEvent.FAILURE_DHCP: + return WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_IP_PROVISIONING; + case WifiMetrics.ConnectionEvent.FAILURE_NETWORK_NOT_FOUND: + return WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NOT_FOUND; + case WifiMetrics.ConnectionEvent.FAILURE_NO_RESPONSE: + return WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NO_RESPONSE; + default: + return WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_UNKNOWN; + } + } + + /** * Clear all internal state (for network settings reset). */ public void clear() { mUserApprovedAccessPointMap.clear(); mApprovedApp = null; + for (RemoteCallbackList<ILocalOnlyConnectionStatusListener> listenerTracker + : mLocalOnlyStatusListenerPerApp.values()) { + listenerTracker.kill(); + } + mLocalOnlyStatusListenerPerApp.clear(); + mFeatureIdPerApp.clear(); Log.i(TAG, "Cleared all internal state"); saveToStore(); } diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index 190502c0be..9e1415b619 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -97,6 +97,7 @@ import android.net.wifi.IDppCallback; import android.net.wifi.IInterfaceCreationInfoCallback; import android.net.wifi.ILastCallerListener; import android.net.wifi.IListListener; +import android.net.wifi.ILocalOnlyConnectionStatusListener; import android.net.wifi.ILocalOnlyHotspotCallback; import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.IOnWifiActivityEnergyInfoListener; @@ -269,6 +270,7 @@ public class WifiServiceImpl extends BaseWifiService { private final ConnectHelper mConnectHelper; private final WifiGlobals mWifiGlobals; private final WifiCarrierInfoManager mWifiCarrierInfoManager; + private final WifiNetworkFactory mWifiNetworkFactory; private @WifiManager.VerboseLoggingLevel int mVerboseLoggingLevel = WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED; private boolean mVerboseLoggingEnabled = false; @@ -498,6 +500,7 @@ public class WifiServiceImpl extends BaseWifiService { mLohsSoftApTracker = new LohsSoftApTracker(); mActiveModeWarden.registerLohsCallback(mLohsSoftApTracker); mWifiNetworkSuggestionsManager = mWifiInjector.getWifiNetworkSuggestionsManager(); + mWifiNetworkFactory = mWifiInjector.getWifiNetworkFactory(); mDppManager = mWifiInjector.getDppManager(); mWifiThreadRunner = mWifiInjector.getWifiThreadRunner(); mWifiHandlerThread = mWifiInjector.getWifiHandlerThread(); @@ -4909,8 +4912,7 @@ public class WifiServiceImpl extends BaseWifiService { // Remove all suggestions from the package. mWifiNetworkSuggestionsManager.removeApp(pkgName); - mWifiInjector.getWifiNetworkFactory().removeUserApprovedAccessPointsForApp( - pkgName); + mWifiInjector.getWifiNetworkFactory().removeApp(pkgName); // Remove all Passpoint profiles from package. mWifiInjector.getPasspointManager().removePasspointProviderWithPackage( @@ -6351,6 +6353,9 @@ public class WifiServiceImpl extends BaseWifiService { */ public void unregisterSuggestionConnectionStatusListener( @NonNull ISuggestionConnectionStatusListener listener, String packageName) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } enforceAccessPermission(); int uid = Binder.getCallingUid(); mWifiPermissionsUtil.checkPackage(uid, packageName); @@ -6363,6 +6368,67 @@ public class WifiServiceImpl extends BaseWifiService { .unregisterSuggestionConnectionStatusListener(listener, packageName, uid)); } + /** + * {@link WifiManager#addLocalOnlyConnectionFailureListener(Executor, WifiManager.LocalOnlyConnectionFailureListener)} + */ + @Override + public void addLocalOnlyConnectionStatusListener(ILocalOnlyConnectionStatusListener listener, + String packageName, String featureId) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + final int uid = Binder.getCallingUid(); + mWifiPermissionsUtil.checkPackage(uid, packageName); + enforceAccessPermission(); + long callingIdentity = Binder.clearCallingIdentity(); + try { + if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { + Log.e(TAG, "UID " + uid + " not visible to the current user"); + throw new SecurityException("UID " + uid + " not visible to the current user"); + } + } finally { + // restore calling identity + Binder.restoreCallingIdentity(callingIdentity); + } + if (mVerboseLoggingEnabled) { + mLog.info("addLocalOnlyConnectionFailureListener uid=%").c(uid).flush(); + } + mWifiThreadRunner.post(() -> + mWifiNetworkFactory.addLocalOnlyConnectionStatusListener(listener, packageName, + featureId)); + } + + /** + * {@link WifiManager#removeLocalOnlyConnectionFailureListener(WifiManager.LocalOnlyConnectionFailureListener)} + */ + @Override + public void removeLocalOnlyConnectionStatusListener(ILocalOnlyConnectionStatusListener listener, + String packageName) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + enforceAccessPermission(); + int uid = Binder.getCallingUid(); + mWifiPermissionsUtil.checkPackage(uid, packageName); + long callingIdentity = Binder.clearCallingIdentity(); + try { + if (!mWifiPermissionsUtil.doesUidBelongToCurrentUserOrDeviceOwner(uid)) { + Log.e(TAG, "UID " + uid + " not visible to the current user"); + throw new SecurityException("UID " + uid + " not visible to the current user"); + } + } finally { + // restore calling identity + Binder.restoreCallingIdentity(callingIdentity); + } + if (mVerboseLoggingEnabled) { + mLog.info("removeLocalOnlyConnectionFailureListener uid=%") + .c(uid).flush(); + } + mWifiThreadRunner.post(() -> + mWifiNetworkFactory.removeLocalOnlyConnectionStatusListener(listener, packageName)); + } + @Override public int calculateSignalLevel(int rssi) { return RssiUtil.calculateSignalLevel(mContext, rssi); diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java index bc21e22d35..b0269e46e7 100644 --- a/service/java/com/android/server/wifi/WifiShellCommand.java +++ b/service/java/com/android/server/wifi/WifiShellCommand.java @@ -580,7 +580,7 @@ public class WifiShellCommand extends BasicShellCommandHandler { } case "network-requests-remove-user-approved-access-points": { String packageName = getNextArgRequired(); - mWifiNetworkFactory.removeUserApprovedAccessPointsForApp(packageName); + mWifiNetworkFactory.removeApp(packageName); return 0; } case "clear-user-disabled-networks": { diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java index 6378178a25..dc9412c138 100644 --- a/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java +++ b/service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java @@ -18,6 +18,7 @@ package com.android.server.wifi; import static android.content.Intent.ACTION_SCREEN_OFF; import static android.content.Intent.ACTION_SCREEN_ON; +import static android.net.wifi.WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_IP_PROVISIONING; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.server.wifi.WifiNetworkFactory.PERIODIC_SCAN_INTERVAL_MS; @@ -73,6 +74,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkProvider; import android.net.NetworkRequest; import android.net.NetworkSpecifier; +import android.net.wifi.ILocalOnlyConnectionStatusListener; import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.INetworkRequestUserSelectionCallback; import android.net.wifi.ScanResult; @@ -181,6 +183,8 @@ public class WifiNetworkFactoryTest extends WifiBaseTest { @Mock PowerManager mPowerManager; @Mock ClientModeImplMonitor mCmiMonitor; @Mock MultiInternetManager mMultiInternetManager; + @Mock ILocalOnlyConnectionStatusListener mLocalOnlyConnectionStatusListener; + private @Mock IBinder mBinder; private @Mock ClientModeManager mPrimaryClientModeManager; private @Mock WifiGlobals mWifiGlobals; private MockitoSession mStaticMockSession = null; @@ -324,6 +328,7 @@ public class WifiNetworkFactoryTest extends WifiBaseTest { // Setup with wifi on. mWifiNetworkFactory.enableVerboseLogging(true); + when(mLocalOnlyConnectionStatusListener.asBinder()).thenReturn(mBinder); } /** @@ -1737,6 +1742,8 @@ public class WifiNetworkFactoryTest extends WifiBaseTest { sendNetworkRequestAndSetupForConnectionStatus(); assertNotNull(mSelectedNetwork); + mWifiNetworkFactory.addLocalOnlyConnectionStatusListener(mLocalOnlyConnectionStatusListener, + TEST_PACKAGE_NAME_1, null); // Send network connection failure indication beyond the retry limit to trigger the failure // handling. @@ -1760,6 +1767,9 @@ public class WifiNetworkFactoryTest extends WifiBaseTest { verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false); verify(mActiveModeWarden).removeClientModeManager(any()); verify(mConnectivityManager).declareNetworkRequestUnfulfillable(eq(mNetworkRequest)); + verify(mLocalOnlyConnectionStatusListener).onConnectionStatus( + eq((WifiNetworkSpecifier) mNetworkRequest.getNetworkSpecifier()), + eq(STATUS_LOCAL_ONLY_CONNECTION_FAILURE_IP_PROVISIONING)); } /** @@ -3060,7 +3070,7 @@ public class WifiNetworkFactoryTest extends WifiBaseTest { mConnectHelper); // 2. Remove all approvals for the app. - mWifiNetworkFactory.removeUserApprovedAccessPointsForApp(TEST_PACKAGE_NAME_1); + mWifiNetworkFactory.removeApp(TEST_PACKAGE_NAME_1); // 3. Second request for the same access point ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0]; diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java index d33ea1edb3..ad0da8877f 100644 --- a/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java +++ b/service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java @@ -141,6 +141,7 @@ import android.net.wifi.IDppCallback; import android.net.wifi.IInterfaceCreationInfoCallback; import android.net.wifi.ILastCallerListener; import android.net.wifi.IListListener; +import android.net.wifi.ILocalOnlyConnectionStatusListener; import android.net.wifi.ILocalOnlyHotspotCallback; import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.IOnWifiActivityEnergyInfoListener; @@ -394,6 +395,7 @@ public class WifiServiceImplTest extends WifiBaseTest { @Mock ICoexCallback mCoexCallback; @Mock IScanResultsCallback mScanResultsCallback; @Mock ISuggestionConnectionStatusListener mSuggestionConnectionStatusListener; + @Mock ILocalOnlyConnectionStatusListener mLocalOnlyConnectionStatusListener; @Mock ISuggestionUserApprovalStatusListener mSuggestionUserApprovalStatusListener; @Mock IOnWifiActivityEnergyInfoListener mOnWifiActivityEnergyInfoListener; @Mock ISubsystemRestartCallback mSubsystemRestartCallback; @@ -5670,7 +5672,7 @@ public class WifiServiceImplTest extends WifiBaseTest { verify(mScanRequestProxy).clearScanRequestTimestampsForApp(packageName, uid); verify(mWifiNetworkSuggestionsManager).removeApp(packageName); - verify(mWifiNetworkFactory).removeUserApprovedAccessPointsForApp(packageName); + verify(mWifiNetworkFactory).removeApp(packageName); verify(mPasspointManager).removePasspointProviderWithPackage(packageName); } @@ -5702,7 +5704,7 @@ public class WifiServiceImplTest extends WifiBaseTest { verify(mScanRequestProxy).clearScanRequestTimestampsForApp(packageName, uid); verify(mWifiNetworkSuggestionsManager).removeApp(packageName); - verify(mWifiNetworkFactory).removeUserApprovedAccessPointsForApp(packageName); + verify(mWifiNetworkFactory).removeApp(packageName); verify(mPasspointManager).removePasspointProviderWithPackage(packageName); } @@ -5736,7 +5738,7 @@ public class WifiServiceImplTest extends WifiBaseTest { verify(mScanRequestProxy).clearScanRequestTimestampsForApp(packageName, uid); verify(mWifiNetworkSuggestionsManager).removeApp(packageName); - verify(mWifiNetworkFactory).removeUserApprovedAccessPointsForApp(packageName); + verify(mWifiNetworkFactory).removeApp(packageName); verify(mPasspointManager).removePasspointProviderWithPackage(packageName); } @@ -5761,7 +5763,7 @@ public class WifiServiceImplTest extends WifiBaseTest { mLooper.dispatchAll(); verify(mScanRequestProxy, never()).clearScanRequestTimestampsForApp(anyString(), anyInt()); verify(mWifiNetworkSuggestionsManager, never()).removeApp(anyString()); - verify(mWifiNetworkFactory, never()).removeUserApprovedAccessPointsForApp(anyString()); + verify(mWifiNetworkFactory, never()).removeApp(anyString()); verify(mPasspointManager, never()).removePasspointProviderWithPackage(anyString()); } @@ -5786,7 +5788,7 @@ public class WifiServiceImplTest extends WifiBaseTest { mLooper.dispatchAll(); verify(mScanRequestProxy, never()).clearScanRequestTimestampsForApp(anyString(), anyInt()); verify(mWifiNetworkSuggestionsManager, never()).removeApp(anyString()); - verify(mWifiNetworkFactory, never()).removeUserApprovedAccessPointsForApp(anyString()); + verify(mWifiNetworkFactory, never()).removeApp(anyString()); verify(mPasspointManager, never()).removePasspointProviderWithPackage(anyString()); } @@ -9013,7 +9015,7 @@ public class WifiServiceImplTest extends WifiBaseTest { verify(mScanRequestProxy).clearScanRequestTimestampsForApp(TEST_PACKAGE_NAME, TEST_UID); verify(mWifiNetworkSuggestionsManager).removeApp(TEST_PACKAGE_NAME); - verify(mWifiNetworkFactory).removeUserApprovedAccessPointsForApp(TEST_PACKAGE_NAME); + verify(mWifiNetworkFactory).removeApp(TEST_PACKAGE_NAME); verify(mPasspointManager).removePasspointProviderWithPackage(TEST_PACKAGE_NAME); } @@ -10942,6 +10944,48 @@ public class WifiServiceImplTest extends WifiBaseTest { () -> mWifiServiceImpl.getChannelData(listener, TEST_PACKAGE_NAME, mExtras)); } + /** + * Test register callback without ACCESS_WIFI_STATE permission. + */ + @Test + public void testRegisterLocalOnlyNetworkCallbackWithMissingAccessWifiPermission() { + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + eq(ACCESS_WIFI_STATE), eq("WifiService")); + assertThrows(SecurityException.class, () -> mWifiServiceImpl + .addLocalOnlyConnectionStatusListener( + mLocalOnlyConnectionStatusListener, TEST_PACKAGE_NAME, TEST_FEATURE_ID)); + } + + /** + * Test unregister callback without permission. + */ + @Test + public void testUnregisterLocalOnlyNetworkCallbackWithMissingPermission() { + doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission( + eq(ACCESS_WIFI_STATE), eq("WifiService")); + assertThrows(SecurityException.class, () -> mWifiServiceImpl + .removeLocalOnlyConnectionStatusListener( + mLocalOnlyConnectionStatusListener, TEST_PACKAGE_NAME)); + } + + /** + * Test register nad unregister callback will go to WifiNetworkSuggestionManager + */ + @Test + public void testRegisterUnregisterLocalOnlyNetworkCallback() throws Exception { + mWifiServiceImpl.addLocalOnlyConnectionStatusListener( + mLocalOnlyConnectionStatusListener, TEST_PACKAGE_NAME, TEST_FEATURE_ID); + mLooper.dispatchAll(); + verify(mWifiNetworkFactory).addLocalOnlyConnectionStatusListener( + eq(mLocalOnlyConnectionStatusListener), eq(TEST_PACKAGE_NAME), eq(TEST_FEATURE_ID) + ); + mWifiServiceImpl.removeLocalOnlyConnectionStatusListener( + mLocalOnlyConnectionStatusListener, TEST_PACKAGE_NAME); + mLooper.dispatchAll(); + verify(mWifiNetworkFactory).removeLocalOnlyConnectionStatusListener( + eq(mLocalOnlyConnectionStatusListener), eq(TEST_PACKAGE_NAME)); + } + private List<ScanResult> createChannelDataScanResults() { List<ScanResult> scanResults = new ArrayList<>(); scanResults.add( |