| /* |
| * Copyright 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 com.android.server.wifi; |
| |
| import static android.net.wifi.WifiManager.EXTRA_SCAN_AVAILABLE; |
| import static android.net.wifi.WifiManager.WIFI_SCAN_AVAILABLE; |
| import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; |
| import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; |
| import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.anyInt; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.eq; |
| import static org.mockito.Mockito.inOrder; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.UserHandle; |
| import android.os.test.TestLooper; |
| import android.support.test.filters.SmallTest; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.List; |
| |
| /** |
| * Unit tests for {@link ScanOnlyModeManager}. |
| */ |
| @SmallTest |
| public class ScanOnlyModeManagerTest { |
| private static final String TAG = "ScanOnlyModeManagerTest"; |
| private static final String TEST_INTERFACE_NAME = "testif0"; |
| private static final String OTHER_INTERFACE_NAME = "notTestIf"; |
| |
| TestLooper mLooper; |
| |
| ScanOnlyModeManager mScanOnlyModeManager; |
| |
| @Mock Context mContext; |
| @Mock WifiMetrics mWifiMetrics; |
| @Mock WifiNative mWifiNative; |
| @Mock ScanOnlyModeManager.Listener mListener; |
| @Mock WifiMonitor mWifiMonitor; |
| @Mock ScanRequestProxy mScanRequestProxy; |
| @Mock WakeupController mWakeupController; |
| @Mock SarManager mSarManager; |
| |
| final ArgumentCaptor<WifiNative.InterfaceCallback> mInterfaceCallbackCaptor = |
| ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class); |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| mLooper = new TestLooper(); |
| |
| mScanOnlyModeManager = createScanOnlyModeManager(); |
| mLooper.dispatchAll(); |
| } |
| |
| private ScanOnlyModeManager createScanOnlyModeManager() { |
| return new ScanOnlyModeManager(mContext, mLooper.getLooper(), mWifiNative, mListener, |
| mWifiMetrics, mScanRequestProxy, mWakeupController, mSarManager); |
| } |
| |
| private void startScanOnlyModeAndVerifyEnabled() throws Exception { |
| when(mWifiNative.setupInterfaceForClientMode(anyBoolean(), any())).thenReturn( |
| TEST_INTERFACE_NAME); |
| mScanOnlyModeManager.start(); |
| mLooper.dispatchAll(); |
| |
| verify(mWifiNative).setupInterfaceForClientMode(eq(true), |
| mInterfaceCallbackCaptor.capture()); |
| |
| // now mark the interface as up |
| mInterfaceCallbackCaptor.getValue().onUp(TEST_INTERFACE_NAME); |
| mLooper.dispatchAll(); |
| |
| ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); |
| verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(), |
| eq(UserHandle.ALL)); |
| List<Intent> intents = intentCaptor.getAllValues(); |
| |
| checkWifiScanStateChangedBroadcast(intents.get(0), WIFI_STATE_DISABLED); |
| checkWifiScanStateChangedBroadcast(intents.get(1), WIFI_STATE_ENABLED); |
| checkWifiStateChangeListenerUpdate(WIFI_STATE_ENABLED); |
| verify(mScanRequestProxy, atLeastOnce()).clearScanResults(); |
| verify(mScanRequestProxy, atLeastOnce()).enableScanningForHiddenNetworks(false); |
| verify(mSarManager).setScanOnlyWifiState(eq(WIFI_STATE_ENABLED)); |
| } |
| |
| private void checkWifiScanStateChangedBroadcast(Intent intent, int expectedCurrentState) { |
| String action = intent.getAction(); |
| assertEquals(WIFI_SCAN_AVAILABLE, action); |
| int currentState = intent.getIntExtra(EXTRA_SCAN_AVAILABLE, WIFI_STATE_UNKNOWN); |
| assertEquals(expectedCurrentState, currentState); |
| } |
| |
| private void checkWifiStateChangeListenerUpdate(int expectedCurrentState) { |
| verify(mListener).onStateChanged(eq(expectedCurrentState)); |
| } |
| |
| /** |
| * ScanMode start sets up an interface in ClientMode for scanning. |
| */ |
| @Test |
| public void scanModeStartAndVerifyEnabled() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| } |
| |
| /** |
| * ScanMode start does not indicate scanning is available when the interface name is empty. |
| */ |
| @Test |
| public void scanModeStartDoesNotSendScanningActiveWhenClientInterfaceNameIsEmpty() |
| throws Exception { |
| when(mWifiNative.setupInterfaceForClientMode(anyBoolean(), any())).thenReturn(""); |
| mScanOnlyModeManager.start(); |
| mLooper.dispatchAll(); |
| |
| verify(mContext, never()).sendStickyBroadcastAsUser(any(), eq(UserHandle.ALL)); |
| checkWifiStateChangeListenerUpdate(WIFI_STATE_UNKNOWN); |
| } |
| |
| /** |
| * Calling ScanOnlyModeManager.start twice does not crash or restart scan mode. |
| */ |
| @Test |
| public void scanOnlyModeStartCalledTwice() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| reset(mWifiNative, mContext); |
| mScanOnlyModeManager.start(); |
| mLooper.dispatchAll(); |
| verifyNoMoreInteractions(mWifiNative, mContext); |
| } |
| |
| /** |
| * ScanMode stop properly cleans up state |
| */ |
| @Test |
| public void scanModeStopCleansUpState() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| reset(mContext, mListener); |
| mScanOnlyModeManager.stop(); |
| mLooper.dispatchAll(); |
| verify(mWifiNative).teardownInterface(TEST_INTERFACE_NAME); |
| verify(mContext, never()).sendStickyBroadcastAsUser(any(), eq(UserHandle.ALL)); |
| verify(mSarManager).setScanOnlyWifiState(eq(WIFI_STATE_DISABLED)); |
| verifyNoMoreInteractions(mListener); |
| } |
| |
| /** |
| * ScanMode properly stops when underlying interface is destroyed. |
| */ |
| @Test |
| public void scanModeStopsOnInterfaceDestroyed() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| reset(mContext); |
| mInterfaceCallbackCaptor.getValue().onDestroyed(TEST_INTERFACE_NAME); |
| mLooper.dispatchAll(); |
| verify(mContext, never()).sendStickyBroadcastAsUser(any(), eq(UserHandle.ALL)); |
| } |
| |
| /** |
| * Calling stop when ScanMode is not started should not send scan state updates |
| */ |
| @Test |
| public void scanModeStopWhenNotStartedDoesNotUpdateScanStateUpdates() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| mScanOnlyModeManager.stop(); |
| mLooper.dispatchAll(); |
| reset(mContext, mListener); |
| |
| // now call stop again |
| mScanOnlyModeManager.stop(); |
| mLooper.dispatchAll(); |
| verify(mContext, never()).sendStickyBroadcastAsUser(any(), any()); |
| verify(mListener, never()).onStateChanged(anyInt()); |
| } |
| |
| /** |
| * Triggering interface down when ScanOnlyMode is active properly exits the active state and |
| * reports an error. |
| */ |
| @Test |
| public void scanModeStartedStopsWhenInterfaceDown() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| reset(mContext); |
| mInterfaceCallbackCaptor.getValue().onDown(TEST_INTERFACE_NAME); |
| mLooper.dispatchAll(); |
| verify(mContext, never()).sendStickyBroadcastAsUser(any(), eq(UserHandle.ALL)); |
| checkWifiStateChangeListenerUpdate(WIFI_STATE_UNKNOWN); |
| verify(mScanRequestProxy).clearScanResults(); |
| } |
| |
| /** |
| * Triggering an interface down for a different interface will not exit scan mode. |
| */ |
| @Test |
| public void scanModeStartedDoesNotStopOnDownForDifferentIface() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| reset(mContext, mListener, mScanRequestProxy); |
| mInterfaceCallbackCaptor.getValue().onDown(OTHER_INTERFACE_NAME); |
| |
| mLooper.dispatchAll(); |
| |
| verifyNoMoreInteractions(mContext, mListener); |
| |
| verify(mScanRequestProxy, never()).clearScanResults(); |
| } |
| |
| /** |
| * Verify that onDestroyed after scan mode is stopped doesn't trigger a callback. |
| */ |
| @Test |
| public void noCallbackOnInterfaceDestroyedWhenAlreadyStopped() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| |
| reset(mListener); |
| |
| mScanOnlyModeManager.stop(); |
| mLooper.dispatchAll(); |
| |
| // now trigger interface destroyed and make sure callback doesn't get called |
| mInterfaceCallbackCaptor.getValue().onDestroyed(TEST_INTERFACE_NAME); |
| mLooper.dispatchAll(); |
| |
| verifyNoMoreInteractions(mListener); |
| } |
| |
| /** |
| * Entering StartedState starts the WakeupController. |
| */ |
| @Test |
| public void scanModeEnterStartsWakeupController() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| |
| verify(mWakeupController).start(); |
| } |
| |
| /** |
| * Exiting StartedState stops the WakeupController. |
| */ |
| @Test |
| public void scanModeExitStopsWakeupController() throws Exception { |
| startScanOnlyModeAndVerifyEnabled(); |
| |
| mScanOnlyModeManager.stop(); |
| mLooper.dispatchAll(); |
| |
| InOrder inOrder = inOrder(mWakeupController, mWifiNative, mListener); |
| |
| inOrder.verify(mWakeupController).start(); |
| inOrder.verify(mWakeupController).stop(); |
| inOrder.verify(mWifiNative).teardownInterface(eq(TEST_INTERFACE_NAME)); |
| } |
| } |