blob: ae58e43be6df86c7fdb86ea3d87441611a7a8157 [file] [log] [blame]
/*
* 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 android.app.cts;
import android.accessibilityservice.AccessibilityService;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.Instrumentation;
import android.app.KeyguardManager;
import android.app.cts.android.app.cts.tools.ServiceConnectionHandler;
import android.app.cts.android.app.cts.tools.ServiceProcessController;
import android.app.cts.android.app.cts.tools.SyncOrderedBroadcast;
import android.app.cts.android.app.cts.tools.UidImportanceListener;
import android.app.cts.android.app.cts.tools.WaitForBroadcast;
import android.app.cts.android.app.cts.tools.WatchUidRunner;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Parcel;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.server.am.WindowManagerState;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiSelector;
import android.test.InstrumentationTestCase;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import com.android.compatibility.common.util.SystemUtil;
public class ActivityManagerProcessStateTest extends InstrumentationTestCase {
private static final String TAG = ActivityManagerProcessStateTest.class.getName();
private static final String STUB_PACKAGE_NAME = "android.app.stubs";
private static final int WAIT_TIME = 2000;
// A secondary test activity from another APK.
static final String SIMPLE_PACKAGE_NAME = "com.android.cts.launcherapps.simpleapp";
static final String SIMPLE_SERVICE = ".SimpleService";
static final String SIMPLE_SERVICE2 = ".SimpleService2";
static final String SIMPLE_RECEIVER_START_SERVICE = ".SimpleReceiverStartService";
static final String SIMPLE_ACTIVITY_START_SERVICE = ".SimpleActivityStartService";
public static String ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT =
"com.android.cts.launcherapps.simpleapp.SimpleActivityStartService.RESULT";
// APKs for testing heavy weight app interactions.
static final String CANT_SAVE_STATE_1_PACKAGE_NAME = "com.android.test.cantsavestate1";
static final String CANT_SAVE_STATE_2_PACKAGE_NAME = "com.android.test.cantsavestate2";
// Actions
static final String ACTION_START_FOREGROUND = "com.android.test.action.START_FOREGROUND";
static final String ACTION_STOP_FOREGROUND = "com.android.test.action.STOP_FOREGROUND";
private static final int TEMP_WHITELIST_DURATION_MS = 2000;
private Context mContext;
private Instrumentation mInstrumentation;
private Intent mServiceIntent;
private Intent mServiceStartForegroundIntent;
private Intent mServiceStopForegroundIntent;
private Intent mService2Intent;
private Intent mMainProcess[];
private Intent mAllProcesses[];
@Override
protected void setUp() throws Exception {
super.setUp();
mInstrumentation = getInstrumentation();
mContext = mInstrumentation.getContext();
mServiceIntent = new Intent();
mServiceIntent.setClassName(SIMPLE_PACKAGE_NAME, SIMPLE_PACKAGE_NAME + SIMPLE_SERVICE);
mServiceStartForegroundIntent = new Intent(mServiceIntent);
mServiceStartForegroundIntent.setAction(ACTION_START_FOREGROUND);
mServiceStopForegroundIntent = new Intent(mServiceIntent);
mServiceStopForegroundIntent.setAction(ACTION_STOP_FOREGROUND);
mService2Intent = new Intent();
mService2Intent.setClassName(SIMPLE_PACKAGE_NAME, SIMPLE_PACKAGE_NAME + SIMPLE_SERVICE2);
mMainProcess = new Intent[1];
mMainProcess[0] = mServiceIntent;
mAllProcesses = new Intent[2];
mAllProcesses[0] = mServiceIntent;
mAllProcesses[1] = mService2Intent;
mContext.stopService(mServiceIntent);
mContext.stopService(mService2Intent);
removeTestAppFromWhitelists();
}
private void removeTestAppFromWhitelists() throws Exception {
executeShellCmd("cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME);
executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
}
private String executeShellCmd(String cmd) throws Exception {
final String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
Log.d(TAG, String.format("Output for '%s': %s", cmd, result));
return result;
}
private boolean isScreenInteractive() {
final PowerManager powerManager =
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
return powerManager.isInteractive();
}
private boolean isKeyguardLocked() {
final KeyguardManager keyguardManager =
(KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
return keyguardManager.isKeyguardLocked();
}
private void waitForAppFocus(String waitForApp, long waitTime) {
long waitUntil = SystemClock.elapsedRealtime() + waitTime;
while (true) {
WindowManagerState wms = new WindowManagerState();
wms.computeState();
String appName = wms.getFocusedApp();
if (appName != null) {
ComponentName comp = ComponentName.unflattenFromString(appName);
if (waitForApp.equals(comp.getPackageName())) {
break;
}
}
if (SystemClock.elapsedRealtime() > waitUntil) {
throw new IllegalStateException("Timed out waiting for focus on app "
+ waitForApp + ", last was " + appName);
}
Log.i(TAG, "Waiting for app focus, current: " + appName);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
};
}
private void startActivityAndWaitForShow(final Intent intent) throws Exception {
getInstrumentation().getUiAutomation().executeAndWaitForEvent(
() -> {
try {
mContext.startActivity(intent);
} catch (Exception e) {
fail("Cannot start activity: " + intent);
}
}, (AccessibilityEvent event) -> event.getEventType()
== AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
, WAIT_TIME);
}
private void maybeClick(UiDevice device, UiSelector sel) {
try { device.findObject(sel).click(); } catch (Throwable ignored) { }
}
private void maybeClick(UiDevice device, BySelector sel) {
try { device.findObject(sel).click(); } catch (Throwable ignored) { }
}
/**
* Test basic state changes as processes go up and down due to services running in them.
*/
public void testUidImportanceListener() throws Exception {
final Parcel data = Parcel.obtain();
ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
WAIT_TIME);
ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent,
WAIT_TIME);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
SIMPLE_PACKAGE_NAME, 0);
UidImportanceListener uidForegroundListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE, WAIT_TIME);
InstrumentationRegistry.getInstrumentation().getUiAutomation().revokeRuntimePermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
boolean gotException = false;
try {
uidForegroundListener.register();
} catch (SecurityException e) {
gotException = true;
}
assertTrue("Expected SecurityException thrown", gotException);
InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
/*
Log.d("XXXX", "Invoke: " + cmd);
Log.d("XXXX", "Result: " + result);
Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+ STUB_PACKAGE_NAME));
*/
uidForegroundListener.register();
UidImportanceListener uidGoneListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
uidGoneListener.register();
WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
WAIT_TIME);
try {
// First kill the processes to start out in a stable state.
conn.bind();
conn2.bind();
IBinder service1 = conn.getServiceIBinder();
IBinder service2 = conn2.getServiceIBinder();
conn.unbind();
conn2.unbind();
try {
service1.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
}
try {
service2.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
}
service1 = service2 = null;
// Wait for uid's processes to go away.
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And wait for the uid report to be gone.
uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
// Now bind and see if we get told about the uid coming in to the foreground.
conn.bind();
uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Also make sure the uid state reports are as expected. Wait for active because
// there may be some intermediate states as the process comes up.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Pull out the service IBinder for a kludy hack...
IBinder service = conn.getServiceIBinder();
// Now unbind and see if we get told about it going to the background.
conn.unbind();
uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Now kill the process and see if we are told about it being gone.
try {
service.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
// It is okay if it is already gone for some reason.
}
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
uidWatcher.expect(WatchUidRunner.CMD_GONE, null);
// Now we are going to try different combinations of binding to two processes to
// see if they are correctly combined together for the app.
// Bring up both services.
conn.bind();
conn2.bind();
uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Bring down one service, app state should remain foreground.
conn2.unbind();
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Bring down other service, app state should now be cached. (If the processes both
// actually get killed immediately, this is also not a correctly behaving system.)
conn.unbind();
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Bring up one service, this should be sufficient to become foreground.
conn2.bind();
uidForegroundListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Bring up other service, should remain foreground.
conn.bind();
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Bring down one service, should remain foreground.
conn.unbind();
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And bringing down other service should put us back to cached.
conn2.unbind();
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
data.recycle();
uidWatcher.finish();
uidForegroundListener.unregister();
uidGoneListener.unregister();
}
}
/**
* Test that background check correctly prevents idle services from running but allows
* whitelisted apps to bypass the check.
*/
public void testBackgroundCheckService() throws Exception {
final Parcel data = Parcel.obtain();
Intent serviceIntent = new Intent();
serviceIntent.setClassName(SIMPLE_PACKAGE_NAME,
SIMPLE_PACKAGE_NAME + SIMPLE_SERVICE);
ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, serviceIntent,
WAIT_TIME);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
/*
Log.d("XXXX", "Invoke: " + cmd);
Log.d("XXXX", "Result: " + result);
Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+ STUB_PACKAGE_NAME));
*/
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
SIMPLE_PACKAGE_NAME, 0);
UidImportanceListener uidForegroundListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE, WAIT_TIME);
uidForegroundListener.register();
UidImportanceListener uidGoneListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY, WAIT_TIME);
uidGoneListener.register();
WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
WAIT_TIME);
// First kill the process to start out in a stable state.
mContext.stopService(serviceIntent);
conn.bind();
IBinder service = conn.getServiceIBinder();
conn.unbind();
try {
service.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
}
service = null;
// Wait for uid's process to go away.
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And wait for the uid report to be gone.
uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
String cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// This is a side-effect of the app op command.
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, "NONE");
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// Make sure app is not yet on whitelist
cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// We will use this to monitor when the service is running.
conn.startMonitoring();
try {
// Try starting the service. Should fail!
boolean failed = false;
try {
mContext.startService(serviceIntent);
} catch (IllegalStateException e) {
failed = true;
}
if (!failed) {
fail("Service was allowed to start while in the background");
}
// Put app on temporary whitelist to see if this allows the service start.
cmd = String.format("cmd deviceidle tempwhitelist -d %d %s",
TEMP_WHITELIST_DURATION_MS, SIMPLE_PACKAGE_NAME);
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// Try starting the service now that the app is whitelisted... should work!
mContext.startService(serviceIntent);
conn.waitForConnect();
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Good, now stop the service and give enough time to get off the temp whitelist.
mContext.stopService(serviceIntent);
conn.waitForDisconnect();
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
// Going off the temp whitelist causes a spurious proc state report... that's
// not ideal, but okay.
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Now that we should be off the temp whitelist, make sure we again can't start.
failed = false;
try {
mContext.startService(serviceIntent);
} catch (IllegalStateException e) {
failed = true;
}
if (!failed) {
fail("Service was allowed to start while in the background");
}
// Now put app on whitelist, should allow service to run.
cmd = "cmd deviceidle whitelist +" + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// Try starting the service now that the app is whitelisted... should work!
mContext.startService(serviceIntent);
conn.waitForConnect();
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Okay, bring down the service.
mContext.stopService(serviceIntent);
conn.waitForDisconnect();
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
mContext.stopService(serviceIntent);
conn.stopMonitoring();
uidWatcher.finish();
cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND allow";
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
uidGoneListener.unregister();
uidForegroundListener.unregister();
data.recycle();
}
}
/**
* Test that background check behaves correctly after a process is no longer foreground:
* first allowing a service to be started, then stopped by the system when idle.
*/
public void testBackgroundCheckStopsService() throws Exception {
final Parcel data = Parcel.obtain();
ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext, mServiceIntent,
WAIT_TIME);
ServiceConnectionHandler conn2 = new ServiceConnectionHandler(mContext, mService2Intent,
WAIT_TIME);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
/*
Log.d("XXXX", "Invoke: " + cmd);
Log.d("XXXX", "Result: " + result);
Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+ STUB_PACKAGE_NAME));
*/
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
SIMPLE_PACKAGE_NAME, 0);
UidImportanceListener uidServiceListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE, WAIT_TIME);
uidServiceListener.register();
UidImportanceListener uidGoneListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED, WAIT_TIME);
uidGoneListener.register();
WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
WAIT_TIME);
// First kill the process to start out in a stable state.
mContext.stopService(mServiceIntent);
mContext.stopService(mService2Intent);
conn.bind();
conn2.bind();
IBinder service = conn.getServiceIBinder();
IBinder service2 = conn2.getServiceIBinder();
conn.unbind();
conn2.unbind();
try {
service.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
}
try {
service2.transact(IBinder.FIRST_CALL_TRANSACTION, data, null, 0);
} catch (RemoteException e) {
}
service = service2 = null;
// Wait for uid's process to go away.
uidGoneListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And wait for the uid report to be gone.
uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
String cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// This is a side-effect of the app op command.
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_NONEXISTENT);
// We don't want to wait for the uid to actually go idle, we can force it now.
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// Make sure app is not yet on whitelist
cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// We will use this to monitor when the service is running.
conn.startMonitoring();
try {
// Try starting the service. Should fail!
boolean failed = false;
try {
mContext.startService(mServiceIntent);
} catch (IllegalStateException e) {
failed = true;
}
if (!failed) {
fail("Service was allowed to start while in the background");
}
// First poke the process into the foreground, so we can avoid background check.
conn2.bind();
conn2.waitForConnect();
// Wait for process state to reflect running service.
uidServiceListener.waitForValue(
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
conn2.unbind();
// Wait for process to recover back down to being cached.
uidServiceListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Try starting the service now that the app is waiting to idle... should work!
mContext.startService(mServiceIntent);
conn.waitForConnect();
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// And also start the second service.
conn2.startMonitoring();
mContext.startService(mService2Intent);
conn2.waitForConnect();
// Force app to go idle now
cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
// Wait for services to be stopped by system.
uidServiceListener.waitForValue(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(SIMPLE_PACKAGE_NAME));
// And service should be stopped by system, so just make sure it is disconnected.
conn.waitForDisconnect();
conn2.waitForDisconnect();
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
// There may be a transient 'SVC' proc state here.
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
mContext.stopService(mServiceIntent);
mContext.stopService(mService2Intent);
conn.cleanup();
conn2.cleanup();
uidWatcher.finish();
cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND allow";
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
uidGoneListener.unregister();
uidServiceListener.unregister();
data.recycle();
}
}
/**
* Test the background check doesn't allow services to be started from broadcasts except
* when in the correct states.
*/
public void testBackgroundCheckBroadcastService() throws Exception {
final Intent broadcastIntent = new Intent();
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setClassName(SIMPLE_PACKAGE_NAME,
SIMPLE_PACKAGE_NAME + SIMPLE_RECEIVER_START_SERVICE);
final ServiceProcessController controller = new ServiceProcessController(mContext,
getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
mServiceIntent, WAIT_TIME);
final WatchUidRunner uidWatcher = controller.getUidWatcher();
try {
// First kill the process to start out in a stable state.
controller.ensureProcessGone();
// Do initial setup.
controller.denyBackgroundOp();
controller.makeUidIdle();
controller.removeFromWhitelist();
// We will use this to monitor when the service is running.
conn.startMonitoring();
// Try sending broadcast to start the service. Should fail!
SyncOrderedBroadcast br = new SyncOrderedBroadcast();
broadcastIntent.putExtra("service", mServiceIntent);
br.sendAndWait(mContext, broadcastIntent, Activity.RESULT_OK, null, null, WAIT_TIME);
int brCode = br.getReceivedCode();
if (brCode != Activity.RESULT_CANCELED) {
fail("Didn't fail starting service, result=" + brCode);
}
// Track the uid proc state changes from the broadcast (but not service execution)
uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null, WAIT_TIME);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_RECEIVER, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY, WAIT_TIME);
// Put app on temporary whitelist to see if this allows the service start.
controller.tempWhitelist(TEMP_WHITELIST_DURATION_MS);
// Being on the whitelist means the uid is now active.
uidWatcher.expect(WatchUidRunner.CMD_ACTIVE, null, WAIT_TIME);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY, WAIT_TIME);
// Try starting the service now that the app is whitelisted... should work!
br.sendAndWait(mContext, broadcastIntent, Activity.RESULT_OK, null, null, WAIT_TIME);
brCode = br.getReceivedCode();
if (brCode != Activity.RESULT_FIRST_USER) {
fail("Failed starting service, result=" + brCode);
}
conn.waitForConnect();
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
// We are going to wait until 'SVC', because we may see an intermediate 'RCVR'
// proc state depending on timing.
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Good, now stop the service and give enough time to get off the temp whitelist.
mContext.stopService(mServiceIntent);
conn.waitForDisconnect();
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
controller.removeFromTempWhitelist();
// Going off the temp whitelist causes a spurious proc state report... that's
// not ideal, but okay.
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// We don't want to wait for the uid to actually go idle, we can force it now.
controller.makeUidIdle();
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
// Make sure the process is gone so we start over fresh.
controller.ensureProcessGone();
// Now that we should be off the temp whitelist, make sure we again can't start.
br.sendAndWait(mContext, broadcastIntent, Activity.RESULT_OK, null, null, WAIT_TIME);
brCode = br.getReceivedCode();
if (brCode != Activity.RESULT_CANCELED) {
fail("Didn't fail starting service, result=" + brCode);
}
// Track the uid proc state changes from the broadcast (but not service execution)
uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null);
// There could be a transient 'cached' state here before 'uncached' if uid state
// changes are dispatched before receiver is started.
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_RECEIVER);
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Now put app on whitelist, should allow service to run.
controller.addToWhitelist();
// Try starting the service now that the app is whitelisted... should work!
br.sendAndWait(mContext, broadcastIntent, Activity.RESULT_OK, null, null, WAIT_TIME);
brCode = br.getReceivedCode();
if (brCode != Activity.RESULT_FIRST_USER) {
fail("Failed starting service, result=" + brCode);
}
conn.waitForConnect();
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Okay, bring down the service.
mContext.stopService(mServiceIntent);
conn.waitForDisconnect();
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
mContext.stopService(mServiceIntent);
conn.stopMonitoringIfNeeded();
controller.cleanup();
}
}
/**
* Test that background check does allow services to be started from activities.
*/
public void testBackgroundCheckActivityService() throws Exception {
final Intent activityIntent = new Intent();
activityIntent.setClassName(SIMPLE_PACKAGE_NAME,
SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY_START_SERVICE);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final ServiceProcessController controller = new ServiceProcessController(mContext,
getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
mServiceIntent, WAIT_TIME);
final WatchUidRunner uidWatcher = controller.getUidWatcher();
try {
// First kill the process to start out in a stable state.
controller.ensureProcessGone();
// Do initial setup.
controller.denyBackgroundOp();
controller.makeUidIdle();
controller.removeFromWhitelist();
// We will use this to monitor when the service is running.
conn.startMonitoring();
// Try starting activity that will start the service. This should be okay.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT);
activityIntent.putExtra("service", mServiceIntent);
mContext.startActivity(activityIntent);
Intent resultIntent = waiter.doWait(WAIT_TIME);
int brCode = resultIntent.getIntExtra("result", Activity.RESULT_CANCELED);
if (brCode != Activity.RESULT_FIRST_USER) {
fail("Failed starting service, result=" + brCode);
}
conn.waitForConnect();
final String expectedActivityState = (isScreenInteractive() && !isKeyguardLocked())
? WatchUidRunner.STATE_TOP : WatchUidRunner.STATE_TOP_SLEEPING;
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, expectedActivityState);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Okay, bring down the service.
mContext.stopService(mServiceIntent);
conn.waitForDisconnect();
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// App isn't yet idle, so we should be able to start the service again.
mContext.startService(mServiceIntent);
conn.waitForConnect();
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// And now fast-forward to the app going idle, service should be stopped.
controller.makeUidIdle();
uidWatcher.waitFor(WatchUidRunner.CMD_IDLE, null);
conn.waitForDisconnect();
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// No longer should be able to start service.
boolean failed = false;
try {
mContext.startService(mServiceIntent);
} catch (IllegalStateException e) {
failed = true;
}
if (!failed) {
fail("Service was allowed to start while in the background");
}
} finally {
mContext.stopService(mServiceIntent);
conn.stopMonitoringIfNeeded();
controller.cleanup();
}
}
/**
* Test that the foreground service app op does prevent the foreground state.
*/
public void testForegroundServiceAppOp() throws Exception {
// Use default timeout value 5000
final ServiceProcessController controller = new ServiceProcessController(mContext,
getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses);
// Use default timeout value 5000
final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
mServiceIntent);
final WatchUidRunner uidWatcher = controller.getUidWatcher();
try {
// First kill the process to start out in a stable state.
controller.ensureProcessGone();
// Do initial setup.
controller.makeUidIdle();
controller.removeFromWhitelist();
controller.setAppOpMode(AppOpsManager.OPSTR_START_FOREGROUND, "allow");
// Put app on whitelist, to allow service to run.
controller.addToWhitelist();
// We will use this to monitor when the service is running.
conn.startMonitoring();
// -------- START SERVICE AND THEN SUCCESSFULLY GO TO FOREGROUND
// Now start the service and wait for it to come up.
mContext.startService(mServiceStartForegroundIntent);
conn.waitForConnect();
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Now take it out of foreground and confirm.
mContext.startService(mServiceStopForegroundIntent);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Good, now stop the service and wait for it to go away.
mContext.stopService(mServiceStartForegroundIntent);
conn.waitForDisconnect();
// There may be a transient STATE_SERVICE we don't care about, so waitFor.
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// We don't want to wait for the uid to actually go idle, we can force it now.
controller.makeUidIdle();
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
// Make sure the process is gone so we start over fresh.
controller.ensureProcessGone();
// -------- START SERVICE AND BLOCK GOING TO FOREGROUND
// Now we will deny the app op and ensure the service can't become foreground.
controller.setAppOpMode(AppOpsManager.OPSTR_START_FOREGROUND, "ignore");
// Now start the service and wait for it to come up.
mContext.startService(mServiceStartForegroundIntent);
conn.waitForConnect();
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Good, now stop the service and wait for it to go away.
mContext.stopService(mServiceStartForegroundIntent);
conn.waitForDisconnect();
// THIS MUST BE AN EXPECT: we want to make sure we don't get in to STATE_FG_SERVICE.
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Make sure the uid is idle (it should be anyway, it never went active here).
controller.makeUidIdle();
// Make sure the process is gone so we start over fresh.
controller.ensureProcessGone();
// -------- DIRECT START FOREGROUND SERVICE SUCCESSFULLY
controller.setAppOpMode(AppOpsManager.OPSTR_START_FOREGROUND, "allow");
// Now start the service and wait for it to come up.
mContext.startForegroundService(mServiceStartForegroundIntent);
conn.waitForConnect();
// Make sure it becomes a foreground service. The process state changes here
// are weird looking because we first need to force the app out of idle to allow
// it to start the service.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
// Remove tempwhitelist avoid temp white list block idle command and app crash occur.
executeShellCmd("cmd deviceidle tempwhitelist -r " + SIMPLE_PACKAGE_NAME);
// Good, now stop the service and wait for it to go away.
mContext.stopService(mServiceStartForegroundIntent);
conn.waitForDisconnect();
// There may be a transient STATE_SERVICE we don't care about, so waitFor.
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// We don't want to wait for the uid to actually go idle, we can force it now.
controller.makeUidIdle();
// Make sure the process is gone so we start over fresh.
controller.ensureProcessGone();
// -------- DIRECT START FOREGROUND SERVICE BLOCKED
// Now we will deny the app op and ensure the service can't become foreground.
controller.setAppOpMode(AppOpsManager.OPSTR_START_FOREGROUND, "ignore");
// But we will put it on the whitelist so the service is still allowed to start.
controller.addToWhitelist();
// Now start the service and wait for it to come up.
mContext.startForegroundService(mServiceStartForegroundIntent);
conn.waitForConnect();
// In this case we only get to run it as a regular service.
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Good, now stop the service and wait for it to go away.
mContext.stopService(mServiceStartForegroundIntent);
conn.waitForDisconnect();
// THIS MUST BE AN EXPECT: we want to make sure we don't get in to STATE_FG_SERVICE.
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Make sure the uid is idle (it should be anyway, it never went active here).
controller.makeUidIdle();
// Make sure the process is gone so we start over fresh.
controller.ensureProcessGone();
// -------- XXX NEED TO TEST NON-WHITELIST CASE WHERE NOTHING HAPPENS
} finally {
mContext.stopService(mServiceStartForegroundIntent);
conn.stopMonitoringIfNeeded();
controller.cleanup();
controller.setAppOpMode(AppOpsManager.OPSTR_START_FOREGROUND, "allow");
controller.removeFromWhitelist();
}
}
private boolean supportsCantSaveState() {
if (mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CANT_SAVE_STATE)) {
return true;
}
// Most types of devices need to support this.
int mode = mContext.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_TYPE_MASK;
if (mode != Configuration.UI_MODE_TYPE_WATCH
&& mode != Configuration.UI_MODE_TYPE_APPLIANCE) {
// Most devices must support the can't save state feature.
throw new IllegalStateException("Devices that are not watches or appliances must "
+ "support FEATURE_CANT_SAVE_STATE");
}
return false;
}
/**
* Test that a single "can't save state" app has the proper process management
* semantics.
*/
public void testCantSaveStateLaunchAndBackground() throws Exception {
if (!supportsCantSaveState()) {
return;
}
final Intent activityIntent = new Intent();
activityIntent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
activityIntent.setAction(Intent.ACTION_MAIN);
activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent homeIntent = new Intent();
homeIntent.setAction(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
// We don't want to wait for the uid to actually go idle, we can force it now.
String cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
// This test is also using UidImportanceListener to make sure the correct
// heavy-weight state is reported there.
UidImportanceListener uidForegroundListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
WAIT_TIME);
uidForegroundListener.register();
UidImportanceListener uidBackgroundListener = new UidImportanceListener(mContext,
appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE-1,
WAIT_TIME);
uidBackgroundListener.register();
WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
WAIT_TIME);
try {
// Start the heavy-weight app, should launch like a normal app.
mContext.startActivity(activityIntent);
// Wait for process state to reflect running activity.
uidForegroundListener.waitForValue(
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// Now go to home, leaving the app. It should be put in the heavy weight state.
mContext.startActivity(homeIntent);
// Wait for process to go down to background heavy-weight.
uidBackgroundListener.waitForValue(
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE,
am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
// While in background, should go in to normal idle state.
// Force app to go idle now
cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
// Switch back to heavy-weight app to see if it correctly returns to foreground.
mContext.startActivity(activityIntent);
// Wait for process state to reflect running activity.
uidForegroundListener.waitForValue(
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,
am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
// Also make sure the uid state reports are as expected.
uidWatcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uidWatcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
// Exit activity, check to see if we are now cached.
getInstrumentation().getUiAutomation().performGlobalAction(
AccessibilityService.GLOBAL_ACTION_BACK);
// Wait for process to become cached
uidBackgroundListener.waitForValue(
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
assertEquals(ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED,
am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
// While in background, should go in to normal idle state.
// Force app to go idle now
cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
} finally {
uidWatcher.finish();
uidForegroundListener.unregister();
uidBackgroundListener.unregister();
}
}
/**
* Test that switching between two "can't save state" apps is handled properly.
*/
public void testCantSaveStateLaunchAndSwitch() throws Exception {
if (!supportsCantSaveState()) {
return;
}
final Intent activity1Intent = new Intent();
activity1Intent.setPackage(CANT_SAVE_STATE_1_PACKAGE_NAME);
activity1Intent.setAction(Intent.ACTION_MAIN);
activity1Intent.addCategory(Intent.CATEGORY_LAUNCHER);
activity1Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent activity2Intent = new Intent();
activity2Intent.setPackage(CANT_SAVE_STATE_2_PACKAGE_NAME);
activity2Intent.setAction(Intent.ACTION_MAIN);
activity2Intent.addCategory(Intent.CATEGORY_LAUNCHER);
activity2Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Intent homeIntent = new Intent();
homeIntent.setAction(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ActivityManager am = mContext.getSystemService(ActivityManager.class);
UiDevice device = UiDevice.getInstance(getInstrumentation());
InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
// We don't want to wait for the uid to actually go idle, we can force it now.
String cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(getInstrumentation(), app1Info.uid,
WAIT_TIME);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
CANT_SAVE_STATE_2_PACKAGE_NAME, 0);
WatchUidRunner uid2Watcher = new WatchUidRunner(getInstrumentation(), app2Info.uid,
WAIT_TIME);
try {
// Start the first heavy-weight app, should launch like a normal app.
mContext.startActivity(activity1Intent);
// Make sure the uid state reports are as expected.
uid1Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// Now go to home, leaving the app. It should be put in the heavy weight state.
mContext.startActivity(homeIntent);
// Wait for process to go down to background heavy-weight.
uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
// Start the second heavy-weight app, should ask us what to do with the two apps
startActivityAndWaitForShow(activity2Intent);
// First, let's try returning to the original app.
maybeClick(device, new UiSelector().resourceId("android:id/switch_old"));
device.waitForIdle();
// App should now be back in foreground.
uid1Watcher.expect(WatchUidRunner.CMD_UNCACHED, null);
uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// Return to home.
mContext.startActivity(homeIntent);
uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
// Again try starting second heavy-weight app to get prompt.
startActivityAndWaitForShow(activity2Intent);
// Now we'll switch to the new app.
maybeClick(device, new UiSelector().resourceId("android:id/switch_new"));
device.waitForIdle();
// The original app should now become cached.
uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
// And the new app should start.
uid2Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// Make sure the original app is idle for cleanliness
cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
uid1Watcher.expect(WatchUidRunner.CMD_IDLE, null);
// Return to home.
mContext.startActivity(homeIntent);
uid2Watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
// Try starting the first heavy weight app, but return to the existing second.
startActivityAndWaitForShow(activity1Intent);
maybeClick(device, new UiSelector().resourceId("android:id/switch_old"));
device.waitForIdle();
uid2Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// Return to home.
mContext.startActivity(homeIntent);
uid2Watcher.waitFor(WatchUidRunner.CMD_CACHED, null);
uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_HEAVY_WEIGHT);
// Again start the first heavy weight app, this time actually switching to it
startActivityAndWaitForShow(activity1Intent);
maybeClick(device, new UiSelector().resourceId("android:id/switch_new"));
device.waitForIdle();
// The second app should now become cached.
uid2Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
// And the first app should start.
uid1Watcher.waitFor(WatchUidRunner.CMD_ACTIVE, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_UNCACHED, null);
uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// Exit activity, check to see if we are now cached.
waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
getInstrumentation().getUiAutomation().performGlobalAction(
AccessibilityService.GLOBAL_ACTION_BACK);
uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
// Make both apps idle for cleanliness.
cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
} finally {
uid2Watcher.finish();
uid1Watcher.finish();
}
}
}