diff --git a/src/com/android/settings/TetherService.java b/src/com/android/settings/TetherService.java index d4944efe50a..1d68e90db98 100644 --- a/src/com/android/settings/TetherService.java +++ b/src/com/android/settings/TetherService.java @@ -37,6 +37,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.TetherUtil; import java.util.ArrayList; @@ -46,7 +47,8 @@ public class TetherService extends Service { private static final String TAG = "TetherService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final String EXTRA_RESULT = "EntitlementResult"; + @VisibleForTesting + public static final String EXTRA_RESULT = "EntitlementResult"; // Activity results to match the activity provision protocol. // Default to something not ok. @@ -295,7 +297,7 @@ public class TetherService extends Service { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Got provision result " + intent); - String provisionResponse = context.getResources().getString( + String provisionResponse = getResources().getString( com.android.internal.R.string.config_mobile_hotspot_provision_response); if (provisionResponse.equals(intent.getAction())) { diff --git a/tests/unit/src/com/android/settings/TetherServiceTest.java b/tests/unit/src/com/android/settings/TetherServiceTest.java new file mode 100644 index 00000000000..09c6119bec6 --- /dev/null +++ b/tests/unit/src/com/android/settings/TetherServiceTest.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2016 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.settings; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE; +import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; +import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE; +import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; +import static android.net.ConnectivityManager.EXTRA_SET_ALARM; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; +import static android.net.ConnectivityManager.TETHERING_INVALID; +import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.os.SystemClock; +import android.test.ServiceTestCase; +import android.test.mock.MockResources; +import android.util.Log; + +import com.android.settings.TetherService; + +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.lang.ref.WeakReference; + +public class TetherServiceTest extends ServiceTestCase { + + private static final String TAG = "TetherServiceTest"; + private static final String TEST_RESPONSE_ACTION = "testProvisioningResponseAction"; + private static final String TEST_NO_UI_ACTION = "testNoUiProvisioningRequestAction"; + private static final int BOGUS_RECEIVER_RESULT = -5; + private static final int TEST_CHECK_PERIOD = 100; + private static final int MS_PER_HOUR = 60 * 60 * 1000; + private static final int SHORT_TIMEOUT = 100; + private static final int PROVISION_TIMEOUT = 1000; + + private TetherService mService; + private MockResources mResources; + int mLastReceiverResultCode = BOGUS_RECEIVER_RESULT; + private int mLastTetherRequestType = TETHERING_INVALID; + private int mProvisionResponse = BOGUS_RECEIVER_RESULT; + private ProvisionReceiver mProvisionReceiver; + private Receiver mResultReceiver; + + @Mock private AlarmManager mAlarmManager; + @Mock private ConnectivityManager mConnectivityManager; + @Mock private WifiManager mWifiManager; + @Mock private SharedPreferences mPrefs; + @Mock private Editor mPrefEditor; + @Captor private ArgumentCaptor mPiCaptor; + @Captor private ArgumentCaptor mStoredTypes; + + public TetherServiceTest() { + super(TetherService.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + + mResources = new MockResources(); + mContext = new TestContextWrapper(getContext()); + setContext(mContext); + + mResultReceiver = new Receiver(this); + mLastReceiverResultCode = BOGUS_RECEIVER_RESULT; + mProvisionResponse = Activity.RESULT_OK; + mProvisionReceiver = new ProvisionReceiver(); + IntentFilter filter = new IntentFilter(TEST_NO_UI_ACTION); + filter.addCategory(Intent.CATEGORY_DEFAULT); + mContext.registerReceiver(mProvisionReceiver, filter); + + final String CURRENT_TYPES = "currentTethers"; + when(mPrefs.getString(CURRENT_TYPES, "")).thenReturn(""); + when(mPrefs.edit()).thenReturn(mPrefEditor); + when(mPrefEditor.putString(eq(CURRENT_TYPES), mStoredTypes.capture())).thenReturn( + mPrefEditor); + } + + @Override + protected void tearDown() throws Exception { + mContext.unregisterReceiver(mProvisionReceiver); + super.tearDown(); + } + + private void cancelAllProvisioning() { + int[] types = new int[]{TETHERING_BLUETOOTH, TETHERING_WIFI, TETHERING_USB}; + for (int type : types) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_REM_TETHER_TYPE, type); + startService(intent); + } + } + + public void testStartForProvision() { + runProvisioningForType(TETHERING_WIFI); + + assertTrue(waitForProvisionRequest(TETHERING_WIFI)); + assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR)); + } + + public void testScheduleRechecks() { + Intent intent = new Intent(); + intent.putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI); + intent.putExtra(EXTRA_SET_ALARM, true); + startService(intent); + + long period = TEST_CHECK_PERIOD * MS_PER_HOUR; + verify(mAlarmManager).setRepeating(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), + eq(period), mPiCaptor.capture()); + PendingIntent pi = mPiCaptor.getValue(); + assertEquals(TetherService.class.getName(), pi.getIntent().getComponent().getClassName()); + } + + public void testStartMultiple() { + runProvisioningForType(TETHERING_WIFI); + + assertTrue(waitForProvisionRequest(TETHERING_WIFI)); + assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR)); + + runProvisioningForType(TETHERING_USB); + + assertTrue(waitForProvisionRequest(TETHERING_USB)); + assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR)); + + runProvisioningForType(TETHERING_BLUETOOTH); + + assertTrue(waitForProvisionRequest(TETHERING_BLUETOOTH)); + assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR)); + } + + public void testPersistTypes() { + runProvisioningForType(TETHERING_WIFI); + + waitForProvisionRequest(TETHERING_WIFI); + waitForProvisionResponse(TETHER_ERROR_NO_ERROR); + + runProvisioningForType(TETHERING_BLUETOOTH); + + waitForProvisionRequest(TETHERING_BLUETOOTH); + waitForProvisionResponse(TETHER_ERROR_NO_ERROR); + + shutdownService(); + assertEquals(TETHERING_WIFI + "," + TETHERING_BLUETOOTH, mStoredTypes.getValue()); + } + + public void testFailureStopsTethering_Wifi() { + mProvisionResponse = Activity.RESULT_CANCELED; + + runProvisioningForType(TETHERING_WIFI); + + assertTrue(waitForProvisionRequest(TETHERING_WIFI)); + assertTrue(waitForProvisionResponse(TETHER_ERROR_PROVISION_FAILED)); + + verify(mWifiManager).setWifiApEnabled(isNull(WifiConfiguration.class), eq(false)); + } + + public void testFailureStopsTethering_Usb() { + mProvisionResponse = Activity.RESULT_CANCELED; + + runProvisioningForType(TETHERING_USB); + + assertTrue(waitForProvisionRequest(TETHERING_USB)); + assertTrue(waitForProvisionResponse(TETHER_ERROR_PROVISION_FAILED)); + + verify(mConnectivityManager).setUsbTethering(eq(false)); + } + + public void testCancelAlarm() { + runProvisioningForType(TETHERING_WIFI); + + assertTrue(waitForProvisionRequest(TETHERING_WIFI)); + assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR)); + + Intent intent = new Intent(); + intent.putExtra(EXTRA_REM_TETHER_TYPE, TETHERING_WIFI); + startService(intent); + + verify(mAlarmManager).cancel(mPiCaptor.capture()); + PendingIntent pi = mPiCaptor.getValue(); + assertEquals(TetherService.class.getName(), pi.getIntent().getComponent().getClassName()); + } + + private void runProvisioningForType(int type) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); + intent.putExtra(EXTRA_RUN_PROVISION, true); + intent.putExtra(EXTRA_PROVISION_CALLBACK, mResultReceiver); + startService(intent); + } + + private boolean waitForProvisionRequest(int expectedType) { + long startTime = SystemClock.uptimeMillis(); + while (true) { + if (mLastTetherRequestType == expectedType) { + mLastTetherRequestType = -1; + return true; + } + if ((SystemClock.uptimeMillis() - startTime) > PROVISION_TIMEOUT) { + Log.v(TAG, String.format( + "waitForProvisionRequest timeout: expected=%d, actual=%d", + expectedType, mLastTetherRequestType)); + return false; + } + SystemClock.sleep(SHORT_TIMEOUT); + } + } + + private boolean waitForProvisionResponse(int expectedValue) { + long startTime = SystemClock.uptimeMillis(); + while (true) { + if (mLastReceiverResultCode == expectedValue) { + mLastReceiverResultCode = BOGUS_RECEIVER_RESULT; + return true; + } + if ((SystemClock.uptimeMillis() - startTime) > PROVISION_TIMEOUT) { + Log.v(TAG, String.format( + "waitForProvisionResponse timeout: expected=%d, actual=%d", + expectedValue, mLastReceiverResultCode)); + return false; + } + SystemClock.sleep(SHORT_TIMEOUT); + } + } + + private static class MockResources extends android.test.mock.MockResources { + @Override + public int getInteger(int id) { + switch(id) { + case com.android.internal.R.integer.config_mobile_hotspot_provision_check_period: + return TEST_CHECK_PERIOD; + default: + return 0; + } + } + + @Override + public String getString(int id) { + switch(id) { + case com.android.internal.R.string.config_mobile_hotspot_provision_response: + return TEST_RESPONSE_ACTION; + case com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui: + return TEST_NO_UI_ACTION; + default: + return null; + } + } + } + + private class TestContextWrapper extends ContextWrapper { + + public TestContextWrapper(Context base) { + super(base); + } + + @Override + public Resources getResources() { + return mResources; + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + // Stub out prefs to control the persisted tether type list. + if (name == "tetherPrefs") { + return mPrefs; + } + return super.getSharedPreferences(name, mode); + } + + @Override + public Object getSystemService(String name) { + if (ALARM_SERVICE.equals(name)) { + return mAlarmManager; + } else if (CONNECTIVITY_SERVICE.equals(name)) { + return mConnectivityManager; + } else if (WIFI_SERVICE.equals(name)) { + return mWifiManager; + } + + return super.getSystemService(name); + } + } + + private static final class Receiver extends ResultReceiver { + final WeakReference mTest; + + Receiver(TetherServiceTest test) { + super(null); + mTest = new WeakReference(test); + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + TetherServiceTest test = mTest.get(); + if (test != null) { + test.mLastReceiverResultCode = resultCode; + } + } + }; + + /** + * Stubs out the provisioning app receiver. + */ + private class ProvisionReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + mLastTetherRequestType = intent.getIntExtra("TETHER_TYPE", TETHERING_INVALID); + sendResponse(mProvisionResponse, context); + } + + private void sendResponse(int response, Context context) { + Intent responseIntent = new Intent(TEST_RESPONSE_ACTION); + responseIntent.putExtra(TetherService.EXTRA_RESULT, response); + context.sendBroadcast( + responseIntent, android.Manifest.permission.CONNECTIVITY_INTERNAL); + } + } +}