| /* |
| * Copyright (C) 2017 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.net; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.frameworks.servicestests.R; |
| import com.android.servicestests.aidl.ICmdReceiverService; |
| import com.android.servicestests.aidl.INetworkStateObserver; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.content.pm.PackageManager; |
| import android.os.BatteryManager; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.SystemClock; |
| import android.provider.Settings; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.filters.LargeTest; |
| import android.support.test.runner.AndroidJUnit4; |
| import android.support.test.uiautomator.UiDevice; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import org.junit.AfterClass; |
| import org.junit.Assert; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.io.IOException; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Tests for verifying network availability on activity start. |
| * |
| * To run the tests, use |
| * |
| * runtest -c com.android.server.net.ConnOnActivityStartTest frameworks-services |
| * |
| * or the following steps: |
| * |
| * Build: m FrameworksServicesTests |
| * Install: adb install -r \ |
| * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk |
| * Run: adb shell am instrument -e class com.android.server.net.ConnOnActivityStartTest -w \ |
| * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner |
| */ |
| @LargeTest |
| @RunWith(AndroidJUnit4.class) |
| public class ConnOnActivityStartTest { |
| private static final String TAG = ConnOnActivityStartTest.class.getSimpleName(); |
| |
| private static final String TEST_PKG = "com.android.servicestests.apps.conntestapp"; |
| private static final String TEST_ACTIVITY_CLASS = TEST_PKG + ".ConnTestActivity"; |
| private static final String TEST_SERVICE_CLASS = TEST_PKG + ".CmdReceiverService"; |
| |
| private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer"; |
| |
| private static final long BATTERY_OFF_TIMEOUT_MS = 2000; // 2 sec |
| private static final long BATTERY_OFF_CHECK_INTERVAL_MS = 200; // 0.2 sec |
| |
| private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec |
| |
| private static final long SCREEN_ON_DELAY_MS = 2000; // 2 sec |
| |
| private static final long BIND_SERVICE_TIMEOUT_SEC = 4; |
| |
| private static final int REPEAT_TEST_COUNT = 5; |
| |
| private static final String KEY_PAROLE_DURATION = "parole_duration"; |
| private static final String DESIRED_PAROLE_DURATION = "0"; |
| |
| private static Context mContext; |
| private static UiDevice mUiDevice; |
| private static int mTestPkgUid; |
| private static BatteryManager mBatteryManager; |
| |
| private static boolean mAppIdleConstsUpdated; |
| private static String mOriginalAppIdleConsts; |
| |
| private static ServiceConnection mServiceConnection; |
| private static ICmdReceiverService mCmdReceiverService; |
| |
| @BeforeClass |
| public static void setUpOnce() throws Exception { |
| mContext = InstrumentationRegistry.getContext(); |
| mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); |
| |
| setDesiredParoleDuration(); |
| mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG, |
| PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); |
| mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0); |
| |
| mBatteryManager = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE); |
| bindService(); |
| } |
| |
| @AfterClass |
| public static void tearDownOnce() { |
| if (mAppIdleConstsUpdated) { |
| Settings.Global.putString(mContext.getContentResolver(), |
| Settings.Global.APP_IDLE_CONSTANTS, mOriginalAppIdleConsts); |
| } |
| unbindService(); |
| } |
| |
| private static void bindService() throws Exception { |
| final CountDownLatch bindLatch = new CountDownLatch(1); |
| mServiceConnection = new ServiceConnection() { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| Log.i(TAG, "Service connected"); |
| mCmdReceiverService = ICmdReceiverService.Stub.asInterface(service); |
| bindLatch.countDown(); |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| Log.i(TAG, "Service disconnected"); |
| } |
| }; |
| final Intent intent = new Intent() |
| .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE_CLASS)); |
| // Needs to use BIND_ALLOW_OOM_MANAGEMENT and BIND_NOT_FOREGROUND so that the test app |
| // does not run in the same process state as this app. |
| mContext.bindService(intent, mServiceConnection, |
| Context.BIND_AUTO_CREATE |
| | Context.BIND_ALLOW_OOM_MANAGEMENT |
| | Context.BIND_NOT_FOREGROUND); |
| if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) { |
| fail("Timed out waiting for the service to bind in " + mTestPkgUid); |
| } |
| } |
| |
| private static void unbindService() { |
| if (mCmdReceiverService != null) { |
| mContext.unbindService(mServiceConnection); |
| } |
| } |
| |
| private static void setDesiredParoleDuration() { |
| mOriginalAppIdleConsts = Settings.Global.getString(mContext.getContentResolver(), |
| Settings.Global.APP_IDLE_CONSTANTS); |
| String newAppIdleConstants; |
| final String newConstant = KEY_PAROLE_DURATION + "=" + DESIRED_PAROLE_DURATION; |
| if (mOriginalAppIdleConsts == null || "null".equals(mOriginalAppIdleConsts)) { |
| // app_idle_constants is initially empty, so just assign the desired value. |
| newAppIdleConstants = newConstant; |
| } else if (mOriginalAppIdleConsts.contains(KEY_PAROLE_DURATION)) { |
| // app_idle_constants contains parole_duration, so replace it with the desired value. |
| newAppIdleConstants = mOriginalAppIdleConsts.replaceAll( |
| KEY_PAROLE_DURATION + "=\\d+", newConstant); |
| } else { |
| // app_idle_constants didn't have parole_duration, so append the desired value. |
| newAppIdleConstants = mOriginalAppIdleConsts + "," + newConstant; |
| } |
| Settings.Global.putString(mContext.getContentResolver(), |
| Settings.Global.APP_IDLE_CONSTANTS, newAppIdleConstants); |
| mAppIdleConstsUpdated = true; |
| } |
| |
| @Test |
| public void testStartActivity_batterySaver() throws Exception { |
| setBatterySaverMode(true); |
| try { |
| testConnOnActivityStart("testStartActivity_batterySaver"); |
| } finally { |
| setBatterySaverMode(false); |
| } |
| } |
| |
| @Test |
| public void testStartActivity_dataSaver() throws Exception { |
| setDataSaverMode(true); |
| try { |
| testConnOnActivityStart("testStartActivity_dataSaver"); |
| } finally { |
| setDataSaverMode(false); |
| } |
| } |
| |
| @Test |
| public void testStartActivity_dozeMode() throws Exception { |
| setDozeMode(true); |
| try { |
| testConnOnActivityStart("testStartActivity_dozeMode"); |
| } finally { |
| setDozeMode(false); |
| } |
| } |
| |
| @Test |
| public void testStartActivity_appStandby() throws Exception { |
| try{ |
| turnBatteryOff(); |
| setAppIdle(true); |
| turnScreenOn(); |
| startActivityAndCheckNetworkAccess(); |
| } finally { |
| turnBatteryOn(); |
| finishActivity(); |
| setAppIdle(false); |
| } |
| } |
| |
| @Test |
| public void testStartActivity_backgroundRestrict() throws Exception { |
| updateRestrictBackgroundBlacklist(true); |
| try { |
| testConnOnActivityStart("testStartActivity_backgroundRestrict"); |
| } finally { |
| updateRestrictBackgroundBlacklist(false); |
| } |
| } |
| |
| private void testConnOnActivityStart(String testName) throws Exception { |
| for (int i = 1; i <= REPEAT_TEST_COUNT; ++i) { |
| try { |
| Log.d(TAG, testName + " Start #" + i); |
| turnScreenOn(); |
| startActivityAndCheckNetworkAccess(); |
| } finally { |
| finishActivity(); |
| Log.d(TAG, testName + " end #" + i); |
| } |
| } |
| } |
| |
| // TODO: Some of these methods are also used in CTS, so instead of duplicating code, |
| // create a static library which can be used by both servicestests and cts. |
| private void setBatterySaverMode(boolean enabled) throws Exception { |
| if (enabled) { |
| turnBatteryOff(); |
| executeCommand("settings put global low_power 1"); |
| } else { |
| executeCommand("settings put global low_power 0"); |
| turnBatteryOn(); |
| } |
| final String result = executeCommand("settings get global low_power"); |
| assertEquals(enabled ? "1" : "0", result); |
| } |
| |
| private void setDataSaverMode(boolean enabled) throws Exception { |
| executeCommand("cmd netpolicy set restrict-background " + enabled); |
| final String output = executeCommand("cmd netpolicy get restrict-background"); |
| final String expectedSuffix = enabled ? "enabled" : "disabled"; |
| assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'", |
| output.endsWith(expectedSuffix)); |
| } |
| |
| private void setDozeMode(boolean enabled) throws Exception { |
| if (enabled) { |
| turnBatteryOff(); |
| turnScreenOff(); |
| executeCommand("dumpsys deviceidle force-idle deep"); |
| } else { |
| turnScreenOn(); |
| turnBatteryOn(); |
| executeCommand("dumpsys deviceidle unforce"); |
| } |
| assertDelayedCommandResult("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE", |
| 5 /* maxTries */, 500 /* napTimeMs */); |
| } |
| |
| private void setAppIdle(boolean enabled) throws Exception { |
| executeCommand("am set-inactive " + TEST_PKG + " " + enabled); |
| assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled, |
| 15 /* maxTries */, 2000 /* napTimeMs */); |
| } |
| |
| private void updateRestrictBackgroundBlacklist(boolean add) throws Exception { |
| if (add) { |
| executeCommand("cmd netpolicy add restrict-background-blacklist " + mTestPkgUid); |
| } else { |
| executeCommand("cmd netpolicy remove restrict-background-blacklist " + mTestPkgUid); |
| } |
| assertRestrictBackground("restrict-background-blacklist", mTestPkgUid, add); |
| } |
| |
| private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception { |
| final int maxTries = 5; |
| boolean actual = false; |
| final String expectedUid = Integer.toString(uid); |
| String uids = ""; |
| for (int i = 1; i <= maxTries; i++) { |
| final String output = executeCommand("cmd netpolicy list " + list); |
| uids = output.split(":")[1]; |
| for (String candidate : uids.split(" ")) { |
| actual = candidate.trim().equals(expectedUid); |
| if (expected == actual) { |
| return; |
| } |
| } |
| Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected " |
| + expected + ", got " + actual + "); sleeping 1s before polling again"); |
| SystemClock.sleep(1000); |
| } |
| fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual |
| + ". Full list: " + uids); |
| } |
| |
| private void turnBatteryOff() throws Exception { |
| executeCommand("cmd battery unplug"); |
| assertBatteryOff(); |
| } |
| |
| private void assertBatteryOff() throws Exception { |
| final long endTime = SystemClock.uptimeMillis() + BATTERY_OFF_TIMEOUT_MS; |
| while (mBatteryManager.isCharging() && SystemClock.uptimeMillis() < endTime) { |
| SystemClock.sleep(BATTERY_OFF_CHECK_INTERVAL_MS); |
| } |
| assertFalse("Power should be disconnected", mBatteryManager.isCharging()); |
| } |
| |
| private void turnBatteryOn() throws Exception { |
| executeCommand("cmd battery reset"); |
| } |
| |
| private void turnScreenOff() throws Exception { |
| executeCommand("input keyevent KEYCODE_SLEEP"); |
| } |
| |
| private void turnScreenOn() throws Exception { |
| executeCommand("input keyevent KEYCODE_WAKEUP"); |
| executeCommand("wm dismiss-keyguard"); |
| // Wait for screen-on state to propagate through the system. |
| SystemClock.sleep(SCREEN_ON_DELAY_MS); |
| } |
| |
| private String executeCommand(String cmd) throws IOException { |
| final String result = executeSilentCommand(cmd); |
| Log.d(TAG, String.format("Result for '%s': %s", cmd, result)); |
| return result; |
| } |
| |
| private static String executeSilentCommand(String cmd) throws IOException { |
| return mUiDevice.executeShellCommand(cmd).trim(); |
| } |
| |
| private void assertDelayedCommandResult(String cmd, String expectedResult, |
| int maxTries, int napTimeMs) throws Exception { |
| String result = ""; |
| for (int i = 1; i <= maxTries; ++i) { |
| result = executeCommand(cmd); |
| if (expectedResult.equals(result)) { |
| return; |
| } |
| Log.v(TAG, "Command '" + cmd + "' returned '" + result + " instead of '" |
| + expectedResult + "' on attempt #" + i |
| + "; sleeping " + napTimeMs + "ms before trying again"); |
| SystemClock.sleep(napTimeMs); |
| } |
| fail("Command '" + cmd + "' did not return '" + expectedResult + "' after " |
| + maxTries + " attempts. Last result: '" + result + "'"); |
| } |
| |
| private void startActivityAndCheckNetworkAccess() throws Exception { |
| final CountDownLatch latch = new CountDownLatch(1); |
| final Intent launchIntent = new Intent().setComponent( |
| new ComponentName(TEST_PKG, TEST_ACTIVITY_CLASS)); |
| final Bundle extras = new Bundle(); |
| final String[] errors = new String[] {null}; |
| extras.putBinder(EXTRA_NETWORK_STATE_OBSERVER, new INetworkStateObserver.Stub() { |
| @Override |
| public void onNetworkStateChecked(String resultData) { |
| errors[0] = resultData; |
| latch.countDown(); |
| } |
| }); |
| launchIntent.putExtras(extras) |
| .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| mContext.startActivity(launchIntent); |
| if (latch.await(NETWORK_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { |
| if (errors[0] != null) { |
| fail("Network not available for test app " + mTestPkgUid + ". " + errors[0]); |
| } |
| } else { |
| fail("Timed out waiting for network availability status from test app " + mTestPkgUid); |
| } |
| } |
| |
| private static void fail(String msg) throws Exception { |
| dumpOnFailure(); |
| Assert.fail(msg); |
| } |
| |
| private static void dumpOnFailure() throws Exception { |
| dump("network_management"); |
| dump("netpolicy"); |
| dumpUsageStats(); |
| } |
| |
| private static void dumpUsageStats() throws Exception { |
| final String output = executeSilentCommand("dumpsys usagestats"); |
| final StringBuilder sb = new StringBuilder(); |
| final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n'); |
| splitter.setString(output); |
| String str; |
| while (splitter.hasNext()) { |
| str = splitter.next(); |
| if (str.contains("package=") && !str.contains(TEST_PKG)) { |
| continue; |
| } |
| if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) { |
| continue; |
| } |
| sb.append(str).append('\n'); |
| } |
| dump("usagestats", sb.toString()); |
| } |
| |
| private static void dump(String service) throws Exception { |
| dump(service, executeSilentCommand("dumpsys " + service)); |
| } |
| |
| private static void dump(String service, String dump) throws Exception { |
| Log.d(TAG, ">>> Begin dump " + service); |
| Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, TAG, dump, null); |
| Log.d(TAG, "<<< End dump " + service); |
| } |
| |
| private void finishActivity() throws Exception { |
| mCmdReceiverService.finishActivity(); |
| } |
| } |