WifiManager: Add callback for traffic poller
The current mechanism for traffic poller directly exposes the wifi
service's internal message handler to privileged apps. Move away from
this mechanism and expose an API to let apps register for callbacks from
traffic poller. This API uses binder IPC for registration and
invocation of callbacks. Clients are automatically removed on binder
death.
This will also let us mark WifiManager#getWifiMessenger() method
private to prevent any apps from getting access to this internal messenger.
Note: This is based on the existing SoftapCallback registration
mechanism.
Also, fixed a bunch of checkstyle errors that were not caught
previously.
Bug: 27074039
Test: Unit tests
Test: Verified the data indicators on Sysui
Change-Id: I505bd2398552be99ccca7b7d3c31a488aa72af70
diff --git a/Android.bp b/Android.bp
index 3957f7e..4a3346c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -576,6 +576,7 @@
"telephony/java/com/android/internal/telephony/euicc/ISetNicknameCallback.aidl",
"telephony/java/com/android/internal/telephony/euicc/ISwitchToProfileCallback.aidl",
"wifi/java/android/net/wifi/ISoftApCallback.aidl",
+ "wifi/java/android/net/wifi/ITrafficStateCallback.aidl",
"wifi/java/android/net/wifi/IWifiManager.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl",
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f729120..8dbdd21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -16,6 +16,12 @@
package com.android.systemui.statusbar.policy;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
+import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
+import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
+import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -55,7 +61,6 @@
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.policy.MobileSignalController.MobileState;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -65,8 +70,6 @@
import java.util.List;
import java.util.Locale;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
/** Platform implementation of the network controller. **/
public class NetworkControllerImpl extends BroadcastReceiver
implements NetworkController, DemoMode, DataUsageController.NetworkNameProvider,
@@ -849,20 +852,20 @@
if (activity != null) {
switch (activity) {
case "inout":
- mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_INOUT);
+ mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT);
break;
case "in":
- mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_IN);
+ mWifiSignalController.setActivity(DATA_ACTIVITY_IN);
break;
case "out":
- mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_OUT);
+ mWifiSignalController.setActivity(DATA_ACTIVITY_OUT);
break;
default:
- mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
+ mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
break;
}
} else {
- mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
+ mWifiSignalController.setActivity(DATA_ACTIVITY_NONE);
}
String ssid = args.getString("ssid");
if (ssid != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index cf80988..0233ad1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -15,21 +15,19 @@
*/
package com.android.systemui.statusbar.policy;
+import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
+import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
+import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
+
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
import android.text.TextUtils;
-import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
import com.android.settingslib.wifi.WifiStatusTracker;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
@@ -37,10 +35,8 @@
import java.util.Objects;
-
public class WifiSignalController extends
SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
- private final AsyncChannel mWifiChannel;
private final boolean mHasMobileData;
private final WifiStatusTracker mWifiTracker;
@@ -57,12 +53,7 @@
connectivityManager, this::handleStatusUpdated);
mWifiTracker.setListening(true);
mHasMobileData = hasMobileData;
- Handler handler = new WifiHandler(Looper.getMainLooper());
- mWifiChannel = new AsyncChannel();
- Messenger wifiMessenger = wifiManager.getWifiServiceMessenger();
- if (wifiMessenger != null) {
- mWifiChannel.connect(context, handler, wifiMessenger);
- }
+ wifiManager.registerTrafficStateCallback(new WifiTrafficStateCallback(), null);
// WiFi only has one state.
mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
"Wi-Fi Icons",
@@ -124,39 +115,20 @@
@VisibleForTesting
void setActivity(int wifiActivity) {
- mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
- || wifiActivity == WifiManager.DATA_ACTIVITY_IN;
- mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
- || wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
+ mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT
+ || wifiActivity == DATA_ACTIVITY_IN;
+ mCurrentState.activityOut = wifiActivity == DATA_ACTIVITY_INOUT
+ || wifiActivity == DATA_ACTIVITY_OUT;
notifyListenersIfNecessary();
}
/**
* Handler to receive the data activity on wifi.
*/
- private class WifiHandler extends Handler {
- WifiHandler(Looper looper) {
- super(looper);
- }
-
+ private class WifiTrafficStateCallback implements WifiManager.TrafficStateCallback {
@Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- mWifiChannel.sendMessage(Message.obtain(this,
- AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
- } else {
- Log.e(mTag, "Failed to connect to wifi");
- }
- break;
- case WifiManager.DATA_ACTIVITY_NOTIFICATION:
- setActivity(msg.arg1);
- break;
- default:
- // Ignore
- break;
- }
+ public void onStateChanged(int state) {
+ setActivity(state);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index dff0665..6e3d906 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -87,15 +87,15 @@
WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel], testSsid);
// Set to different activity state first to ensure a callback happens.
- setWifiActivity(WifiManager.DATA_ACTIVITY_IN);
+ setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN);
- setWifiActivity(WifiManager.DATA_ACTIVITY_NONE);
+ setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE);
verifyLastQsDataDirection(false, false);
- setWifiActivity(WifiManager.DATA_ACTIVITY_IN);
+ setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN);
verifyLastQsDataDirection(true, false);
- setWifiActivity(WifiManager.DATA_ACTIVITY_OUT);
+ setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT);
verifyLastQsDataDirection(false, true);
- setWifiActivity(WifiManager.DATA_ACTIVITY_INOUT);
+ setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT);
verifyLastQsDataDirection(true, true);
}
diff --git a/wifi/java/android/net/wifi/ITrafficStateCallback.aidl b/wifi/java/android/net/wifi/ITrafficStateCallback.aidl
new file mode 100644
index 0000000..0c8e777
--- /dev/null
+++ b/wifi/java/android/net/wifi/ITrafficStateCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+/**
+ * Interface for Traffic state callback.
+ *
+ * @hide
+ */
+oneway interface ITrafficStateCallback
+{
+ /**
+ * Callback invoked to inform clients about the current traffic state.
+ *
+ * @param state One of the values: {@link #DATA_ACTIVITY_NONE}, {@link #DATA_ACTIVITY_IN},
+ * {@link #DATA_ACTIVITY_OUT} & {@link #DATA_ACTIVITY_INOUT}.
+ * @hide
+ */
+ void onStateChanged(int state);
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index af44b7e..5631919 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -26,6 +26,7 @@
import android.net.DhcpInfo;
import android.net.Network;
import android.net.wifi.ISoftApCallback;
+import android.net.wifi.ITrafficStateCallback;
import android.net.wifi.PasspointManagementObjectDefinition;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiActivityEnergyInfo;
@@ -180,5 +181,9 @@
void registerSoftApCallback(in IBinder binder, in ISoftApCallback callback, int callbackIdentifier);
void unregisterSoftApCallback(int callbackIdentifier);
+
+ void registerTrafficStateCallback(in IBinder binder, in ITrafficStateCallback callback, int callbackIdentifier);
+
+ void unregisterTrafficStateCallback(int callbackIdentifier);
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 922e025..43f0e50 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -21,7 +21,6 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -32,9 +31,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
-import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.ProvisioningCallback;
import android.os.Binder;
import android.os.Build;
@@ -920,22 +919,6 @@
*/
public static final int WIFI_FREQUENCY_BAND_2GHZ = 2;
- /** List of asyncronous notifications
- * @hide
- */
- public static final int DATA_ACTIVITY_NOTIFICATION = 1;
-
- //Lowest bit indicates data reception and the second lowest
- //bit indicates data transmitted
- /** @hide */
- public static final int DATA_ACTIVITY_NONE = 0x00;
- /** @hide */
- public static final int DATA_ACTIVITY_IN = 0x01;
- /** @hide */
- public static final int DATA_ACTIVITY_OUT = 0x02;
- /** @hide */
- public static final int DATA_ACTIVITY_INOUT = 0x03;
-
/** @hide */
public static final boolean DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED = false;
@@ -2460,7 +2443,7 @@
}
@Override
- public void onStateChanged(int state, int failureReason) throws RemoteException {
+ public void onStateChanged(int state, int failureReason) {
Log.v(TAG, "SoftApCallbackProxy: onStateChanged: state=" + state + ", failureReason=" +
failureReason);
mHandler.post(() -> {
@@ -2469,7 +2452,7 @@
}
@Override
- public void onNumClientsChanged(int numClients) throws RemoteException {
+ public void onNumClientsChanged(int numClients) {
Log.v(TAG, "SoftApCallbackProxy: onNumClientsChanged: numClients=" + numClients);
mHandler.post(() -> {
mCallback.onNumClientsChanged(numClients);
@@ -3102,9 +3085,8 @@
* an AsyncChannel communication with WifiService
*
* @return Messenger pointing to the WifiService handler
- * @hide
*/
- public Messenger getWifiServiceMessenger() {
+ private Messenger getWifiServiceMessenger() {
try {
return mService.getWifiServiceMessenger(mContext.getOpPackageName());
} catch (RemoteException e) {
@@ -3718,4 +3700,107 @@
});
}
}
+
+ /**
+ * Base class for Traffic state callback. Should be extended by applications and set when
+ * calling {@link WifiManager#registerTrafficStateCallback(TrafficStateCallback, Handler)}.
+ * @hide
+ */
+ public interface TrafficStateCallback {
+ /**
+ * Lowest bit indicates data reception and the second lowest
+ * bit indicates data transmitted
+ */
+ /** @hide */
+ int DATA_ACTIVITY_NONE = 0x00;
+ /** @hide */
+ int DATA_ACTIVITY_IN = 0x01;
+ /** @hide */
+ int DATA_ACTIVITY_OUT = 0x02;
+ /** @hide */
+ int DATA_ACTIVITY_INOUT = 0x03;
+
+ /**
+ * Callback invoked to inform clients about the current traffic state.
+ *
+ * @param state One of the values: {@link #DATA_ACTIVITY_NONE}, {@link #DATA_ACTIVITY_IN},
+ * {@link #DATA_ACTIVITY_OUT} & {@link #DATA_ACTIVITY_INOUT}.
+ * @hide
+ */
+ void onStateChanged(int state);
+ }
+
+ /**
+ * Callback proxy for TrafficStateCallback objects.
+ *
+ * @hide
+ */
+ private static class TrafficStateCallbackProxy extends ITrafficStateCallback.Stub {
+ private final Handler mHandler;
+ private final TrafficStateCallback mCallback;
+
+ TrafficStateCallbackProxy(Looper looper, TrafficStateCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ Log.v(TAG, "TrafficStateCallbackProxy: onStateChanged state=" + state);
+ mHandler.post(() -> {
+ mCallback.onStateChanged(state);
+ });
+ }
+ }
+
+ /**
+ * Registers a callback for monitoring traffic state. See {@link TrafficStateCallback}. These
+ * callbacks will be invoked periodically by platform to inform clients about the current
+ * traffic state. Caller can unregister a previously registered callback using
+ * {@link #unregisterTrafficStateCallback(TrafficStateCallback)}
+ * <p>
+ * Applications should have the
+ * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers
+ * without the permission will trigger a {@link java.lang.SecurityException}.
+ * <p>
+ *
+ * @param callback Callback for traffic state events
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code callback}
+ * object. If null, then the application's main thread will be used.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void registerTrafficStateCallback(@NonNull TrafficStateCallback callback,
+ @Nullable Handler handler) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "registerTrafficStateCallback: callback=" + callback + ", handler=" + handler);
+
+ Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper();
+ Binder binder = new Binder();
+ try {
+ mService.registerTrafficStateCallback(
+ binder, new TrafficStateCallbackProxy(looper, callback), callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allow callers to unregister a previously registered callback. After calling this method,
+ * applications will no longer receive traffic state notifications.
+ *
+ * @param callback Callback to unregister for traffic state events
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void unregisterTrafficStateCallback(@NonNull TrafficStateCallback callback) {
+ if (callback == null) throw new IllegalArgumentException("callback cannot be null");
+ Log.v(TAG, "unregisterTrafficStateCallback: callback=" + callback);
+
+ try {
+ mService.unregisterTrafficStateCallback(callback.hashCode());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 20e49cd..5058080 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -25,9 +25,6 @@
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED;
import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.REQUEST_REGISTERED;
import static android.net.wifi.WifiManager.SAP_START_FAILURE_GENERAL;
-import static android.net.wifi.WifiManager.SAP_START_FAILURE_NO_CHANNEL;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
@@ -47,6 +44,7 @@
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
import android.net.wifi.WifiManager.LocalOnlyHotspotSubscription;
import android.net.wifi.WifiManager.SoftApCallback;
+import android.net.wifi.WifiManager.TrafficStateCallback;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -57,6 +55,7 @@
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -77,6 +76,7 @@
@Mock WifiConfiguration mApConfig;
@Mock IBinder mAppBinder;
@Mock SoftApCallback mSoftApCallback;
+ @Mock TrafficStateCallback mTrafficStateCallback;
private Handler mHandler;
private TestLooper mLooper;
@@ -702,7 +702,7 @@
}
/*
- * Verify client provided callback is being called through callback proxy
+ * Verify client-provided callback is being called through callback proxy
*/
@Test
public void softApCallbackProxyCallsOnStateChanged() throws Exception {
@@ -718,7 +718,7 @@
}
/*
- * Verify client provided callback is being called through callback proxy
+ * Verify client-provided callback is being called through callback proxy
*/
@Test
public void softApCallbackProxyCallsOnNumClientsChanged() throws Exception {
@@ -735,7 +735,7 @@
}
/*
- * Verify client provided callback is being called through callback proxy on multiple events
+ * Verify client-provided callback is being called through callback proxy on multiple events
*/
@Test
public void softApCallbackProxyCallsOnMultipleUpdates() throws Exception {
@@ -757,7 +757,7 @@
}
/*
- * Verify client provided callback is being called on the correct thread
+ * Verify client-provided callback is being called on the correct thread
*/
@Test
public void softApCallbackIsCalledOnCorrectThread() throws Exception {
@@ -1082,4 +1082,84 @@
when(mWifiService.startScan(TEST_PACKAGE_NAME)).thenReturn(false);
assertFalse(mWifiManager.startScan());
}
+
+ /**
+ * Verify main looper is used when handler is not provided.
+ */
+ @Test
+ public void registerTrafficStateCallbackUsesMainLooperOnNullArgumentForHandler()
+ throws Exception {
+ when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ ArgumentCaptor<ITrafficStateCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class);
+ mWifiManager.registerTrafficStateCallback(mTrafficStateCallback, null);
+ verify(mWifiService).registerTrafficStateCallback(
+ any(IBinder.class), callbackCaptor.capture(), anyInt());
+
+ assertEquals(0, mLooper.dispatchAll());
+ callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT);
+ assertEquals(1, mLooper.dispatchAll());
+ verify(mTrafficStateCallback).onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT);
+ }
+
+ /**
+ * Verify the call to unregisterTrafficStateCallback goes to WifiServiceImpl.
+ */
+ @Test
+ public void unregisterTrafficStateCallbackCallGoesToWifiServiceImpl() throws Exception {
+ ArgumentCaptor<Integer> callbackIdentifier = ArgumentCaptor.forClass(Integer.class);
+ mWifiManager.registerTrafficStateCallback(mTrafficStateCallback, mHandler);
+ verify(mWifiService).registerTrafficStateCallback(any(IBinder.class),
+ any(ITrafficStateCallback.Stub.class), callbackIdentifier.capture());
+
+ mWifiManager.unregisterTrafficStateCallback(mTrafficStateCallback);
+ verify(mWifiService).unregisterTrafficStateCallback(
+ eq((int) callbackIdentifier.getValue()));
+ }
+
+ /*
+ * Verify client-provided callback is being called through callback proxy on multiple events
+ */
+ @Test
+ public void trafficStateCallbackProxyCallsOnMultipleUpdates() throws Exception {
+ ArgumentCaptor<ITrafficStateCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class);
+ mWifiManager.registerTrafficStateCallback(mTrafficStateCallback, mHandler);
+ verify(mWifiService).registerTrafficStateCallback(
+ any(IBinder.class), callbackCaptor.capture(), anyInt());
+
+ InOrder inOrder = inOrder(mTrafficStateCallback);
+
+ callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN);
+ callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT);
+ callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT);
+
+ mLooper.dispatchAll();
+ inOrder.verify(mTrafficStateCallback).onStateChanged(
+ TrafficStateCallback.DATA_ACTIVITY_IN);
+ inOrder.verify(mTrafficStateCallback).onStateChanged(
+ TrafficStateCallback.DATA_ACTIVITY_INOUT);
+ inOrder.verify(mTrafficStateCallback).onStateChanged(
+ TrafficStateCallback.DATA_ACTIVITY_OUT);
+ }
+
+ /*
+ * Verify client-provided callback is being called on the correct thread
+ */
+ @Test
+ public void trafficStateCallbackIsCalledOnCorrectThread() throws Exception {
+ ArgumentCaptor<ITrafficStateCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class);
+ TestLooper altLooper = new TestLooper();
+ Handler altHandler = new Handler(altLooper.getLooper());
+ mWifiManager.registerTrafficStateCallback(mTrafficStateCallback, altHandler);
+ verify(mContext, never()).getMainLooper();
+ verify(mWifiService).registerTrafficStateCallback(
+ any(IBinder.class), callbackCaptor.capture(), anyInt());
+
+ assertEquals(0, altLooper.dispatchAll());
+ callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT);
+ assertEquals(1, altLooper.dispatchAll());
+ verify(mTrafficStateCallback).onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT);
+ }
}