/* * 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_PREVIOUS_WIFI_STATE; import static android.net.wifi.WifiManager.EXTRA_SCAN_AVAILABLE; import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE; import static android.net.wifi.WifiManager.WIFI_SCAN_AVAILABLE; import static android.net.wifi.WifiManager.WIFI_STATE_CHANGED_ACTION; import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; import android.content.Context; import android.content.Intent; import android.os.UserHandle; import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.List; /** * Unit tests for {@link ClientModeManager}. */ @SmallTest public class ClientModeManagerTest { private static final String TAG = "ClientModeManagerTest"; private static final String TEST_INTERFACE_NAME = "testif0"; private static final String OTHER_INTERFACE_NAME = "notTestIf"; TestLooper mLooper; ClientModeManager mClientModeManager; @Mock Context mContext; @Mock WifiMetrics mWifiMetrics; @Mock WifiNative mWifiNative; @Mock ClientModeManager.Listener mListener; @Mock WifiMonitor mWifiMonitor; @Mock ScanRequestProxy mScanRequestProxy; @Mock WifiStateMachine mWifiStateMachine; final ArgumentCaptor mInterfaceCallbackCaptor = ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class); @Before public void setUp() { MockitoAnnotations.initMocks(this); mLooper = new TestLooper(); mClientModeManager = createClientModeManager(); mLooper.dispatchAll(); } private ClientModeManager createClientModeManager() { return new ClientModeManager(mContext, mLooper.getLooper(), mWifiNative, mListener, mWifiMetrics, mScanRequestProxy, mWifiStateMachine); } private void startClientModeAndVerifyEnabled() throws Exception { ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); when(mWifiNative.setupInterfaceForClientMode(eq(false), any())) .thenReturn(TEST_INTERFACE_NAME); mClientModeManager.start(); mLooper.dispatchAll(); verify(mWifiNative).setupInterfaceForClientMode( eq(false), mInterfaceCallbackCaptor.capture()); // now mark the interface as up mInterfaceCallbackCaptor.getValue().onUp(TEST_INTERFACE_NAME); mLooper.dispatchAll(); verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL)); List intents = intentCaptor.getAllValues(); assertEquals(4, intents.size()); Log.d(TAG, "captured intents: " + intents); checkWifiStateChangedBroadcast(intents.get(0), WIFI_STATE_ENABLING, WIFI_STATE_DISABLED); checkWifiScanStateChangedBroadcast(intents.get(1), WIFI_STATE_DISABLED); checkWifiScanStateChangedBroadcast(intents.get(2), WIFI_STATE_ENABLED); checkWifiStateChangedBroadcast(intents.get(3), WIFI_STATE_ENABLED, WIFI_STATE_ENABLING); checkWifiStateChangeListenerUpdate(WIFI_STATE_ENABLED); verify(mScanRequestProxy, atLeastOnce()).enableScanningForHiddenNetworks(true); verify(mScanRequestProxy).clearScanResults(); } 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 checkWifiStateChangedBroadcast( Intent intent, int expectedCurrentState, int expectedPrevState) { String action = intent.getAction(); assertEquals(WIFI_STATE_CHANGED_ACTION, action); int currentState = intent.getIntExtra(EXTRA_WIFI_STATE, WIFI_STATE_UNKNOWN); assertEquals(expectedCurrentState, currentState); int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_STATE, WIFI_STATE_UNKNOWN); assertEquals(expectedPrevState, prevState); verify(mWifiStateMachine, atLeastOnce()).setWifiStateForApiCalls(expectedCurrentState); } private void checkWifiStateChangeListenerUpdate(int expectedCurrentState) { verify(mListener).onStateChanged(eq(expectedCurrentState)); } private void verifyNotificationsForCleanShutdown(int fromState) { ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, atLeastOnce()) .sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL)); List intents = intentCaptor.getAllValues(); assertEquals(2, intents.size()); checkWifiStateChangedBroadcast(intents.get(0), WIFI_STATE_DISABLING, fromState); checkWifiStateChangedBroadcast(intents.get(1), WIFI_STATE_DISABLED, WIFI_STATE_DISABLING); } private void verifyNotificationsForFailure() { ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, atLeastOnce()) .sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL)); List intents = intentCaptor.getAllValues(); assertEquals(2, intents.size()); checkWifiStateChangedBroadcast(intents.get(0), WIFI_STATE_DISABLING, WIFI_STATE_UNKNOWN); checkWifiStateChangedBroadcast(intents.get(1), WIFI_STATE_DISABLED, WIFI_STATE_DISABLING); checkWifiStateChangeListenerUpdate(WIFI_STATE_UNKNOWN); } /** * ClientMode start sets up an interface in ClientMode. */ @Test public void clientModeStartCreatesClientInterface() throws Exception { startClientModeAndVerifyEnabled(); } /** * ClientMode increments failure metrics when failing to setup client mode. */ @Test public void detectAndReportErrorWhenSetupForClientWifiNativeFailure() throws Exception { when(mWifiNative.setupInterfaceForClientMode(eq(false), any())).thenReturn(null); mClientModeManager.start(); mLooper.dispatchAll(); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL)); List intents = intentCaptor.getAllValues(); assertEquals(2, intents.size()); checkWifiStateChangedBroadcast(intents.get(0), WIFI_STATE_ENABLING, WIFI_STATE_DISABLED); checkWifiStateChangedBroadcast(intents.get(1), WIFI_STATE_DISABLED, WIFI_STATE_UNKNOWN); checkWifiStateChangeListenerUpdate(WIFI_STATE_UNKNOWN); } /** * ClientMode start does not indicate scanning is available when the interface name is empty. */ @Test public void clientModeStartDoesNotSendScanningActiveWhenClientInterfaceNameIsEmpty() throws Exception { when(mWifiNative.setupInterfaceForClientMode(eq(false), any())).thenReturn(""); mClientModeManager.start(); mLooper.dispatchAll(); ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL)); List intents = intentCaptor.getAllValues(); assertEquals(2, intents.size()); checkWifiStateChangedBroadcast(intents.get(0), WIFI_STATE_ENABLING, WIFI_STATE_DISABLED); checkWifiStateChangedBroadcast(intents.get(1), WIFI_STATE_DISABLED, WIFI_STATE_UNKNOWN); checkWifiStateChangeListenerUpdate(WIFI_STATE_UNKNOWN); } /** * Calling ClientModeManager.start twice does not crash or restart client mode. */ @Test public void clientModeStartCalledTwice() throws Exception { startClientModeAndVerifyEnabled(); reset(mWifiNative, mContext); mClientModeManager.start(); mLooper.dispatchAll(); verifyNoMoreInteractions(mWifiNative, mContext); } /** * ClientMode stop properly cleans up state */ @Test public void clientModeStopCleansUpState() throws Exception { startClientModeAndVerifyEnabled(); reset(mContext, mListener); mClientModeManager.stop(); mLooper.dispatchAll(); verifyNotificationsForCleanShutdown(WIFI_STATE_ENABLED); // on an explicit stop, we should not trigger the callback verifyNoMoreInteractions(mListener); } /** * Calling stop when ClientMode is not started should not send scan state updates */ @Test public void clientModeStopWhenNotStartedDoesNotUpdateScanStateUpdates() throws Exception { startClientModeAndVerifyEnabled(); reset(mContext); mClientModeManager.stop(); mLooper.dispatchAll(); verifyNotificationsForCleanShutdown(WIFI_STATE_ENABLED); reset(mContext, mListener); // now call stop again mClientModeManager.stop(); mLooper.dispatchAll(); verify(mContext, never()).sendStickyBroadcastAsUser(any(), any()); verify(mListener, never()).onStateChanged(anyInt()); } /** * Triggering interface down when ClientMode is active properly exits the active state. */ @Test public void clientModeStartedStopsWhenInterfaceDown() throws Exception { startClientModeAndVerifyEnabled(); reset(mContext, mScanRequestProxy); when(mWifiStateMachine.isConnectedMacRandomizationEnabled()).thenReturn(false); mInterfaceCallbackCaptor.getValue().onDown(TEST_INTERFACE_NAME); mLooper.dispatchAll(); verify(mWifiStateMachine).failureDetected(eq(SelfRecovery.REASON_STA_IFACE_DOWN)); verifyNotificationsForFailure(); } /** * Triggering interface down when ClientMode is active and Connected MacRandomization is enabled * does not exit the active state. */ @Test public void clientModeStartedWithConnectedMacRandDoesNotStopWhenInterfaceDown() throws Exception { startClientModeAndVerifyEnabled(); reset(mContext, mScanRequestProxy); when(mWifiStateMachine.isConnectedMacRandomizationEnabled()).thenReturn(true); mInterfaceCallbackCaptor.getValue().onDown(TEST_INTERFACE_NAME); mLooper.dispatchAll(); verify(mWifiStateMachine, never()).failureDetected(eq(SelfRecovery.REASON_STA_IFACE_DOWN)); verify(mContext, never()).sendStickyBroadcastAsUser(any(), any()); } /** * Testing the handling of an interface destroyed notification. */ @Test public void clientModeStartedStopsOnInterfaceDestroyed() throws Exception { startClientModeAndVerifyEnabled(); reset(mContext, mScanRequestProxy, mListener); mInterfaceCallbackCaptor.getValue().onDestroyed(TEST_INTERFACE_NAME); mLooper.dispatchAll(); verifyNotificationsForCleanShutdown(WIFI_STATE_ENABLED); verify(mWifiStateMachine).handleIfaceDestroyed(); } /** * Verify that onDestroyed after client mode is stopped doesn't trigger a callback. */ @Test public void noCallbackOnInterfaceDestroyedWhenAlreadyStopped() throws Exception { startClientModeAndVerifyEnabled(); reset(mListener); mClientModeManager.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); verify(mWifiStateMachine, never()).handleIfaceDestroyed(); } }