summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nate Jiang <qiangjiang@google.com> 2023-01-12 11:24:30 -0800
committer Nate(Qiang) Jiang <qiangjiang@google.com> 2023-01-18 06:08:43 +0000
commitee93b25ab0557a0e4bc09a760ca3ea096240de07 (patch)
tree1e2e20a7d08c6f1ad9f9afdcd20ea59367706626
parentfe723e662e14a59b0613343d6ed0798a71e8e82e (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
-rw-r--r--framework/aidl-export/android/net/wifi/WifiNetworkSpecifier.aidl19
-rw-r--r--framework/api/current.txt12
-rw-r--r--framework/java/android/net/wifi/BaseWifiService.java12
-rw-r--r--framework/java/android/net/wifi/ILocalOnlyConnectionStatusListener.aidl29
-rw-r--r--framework/java/android/net/wifi/IWifiManager.aidl5
-rw-r--r--framework/java/android/net/wifi/WifiManager.java151
-rw-r--r--framework/tests/src/android/net/wifi/WifiManagerTest.java20
-rw-r--r--service/java/com/android/server/wifi/WifiNetworkFactory.java110
-rw-r--r--service/java/com/android/server/wifi/WifiServiceImpl.java70
-rw-r--r--service/java/com/android/server/wifi/WifiShellCommand.java2
-rw-r--r--service/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java12
-rw-r--r--service/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java56
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(