blob: ca6ddfd615170db342b8b54f22062805786520af [file] [log] [blame]
/*
* 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<WifiNative.InterfaceCallback> 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<Intent> 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<Intent> 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<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, atLeastOnce())
.sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL));
List<Intent> 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<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, atLeastOnce())
.sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL));
List<Intent> 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<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(),
eq(UserHandle.ALL));
List<Intent> 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<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, atLeastOnce()).sendStickyBroadcastAsUser(intentCaptor.capture(),
eq(UserHandle.ALL));
List<Intent> 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();
}
}