blob: df80423e93e94007067d3396de7b27b00c52c1c0 [file] [log] [blame]
/*
* Copyright (C) 2022 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 static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
import android.accessibilityservice.AccessibilityService;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.cts.android.app.cts.tools.WatchUidRunner;
import android.app.stubs.CommandReceiver;
import android.app.stubs.LocalForegroundService;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.permission.cts.PermissionUtils;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ActivityManagerFgsDelegateTest {
private static final String TAG = ActivityManagerFgsDelegateTest.class.getName();
static final String STUB_PACKAGE_NAME = "android.app.stubs";
static final String PACKAGE_NAME_APP1 = "com.android.app1";
static final int WAITFOR_MSEC = 10000;
private static final String[] PACKAGE_NAMES = {
PACKAGE_NAME_APP1
};
private static final String DUMP_COMMAND = "dumpsys activity services " + PACKAGE_NAME_APP1
+ "/SPECIAL_USE:FgsDelegate";
private static final String DUMP_COMMAND2 = "dumpsys activity services " + PACKAGE_NAME_APP1
+ "/android.app.stubs.LocalForegroundService";
private Context mContext;
private Instrumentation mInstrumentation;
private Context mTargetContext;
ActivityManager mActivityManager;
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mContext = mInstrumentation.getContext();
mActivityManager = mContext.getSystemService(ActivityManager.class);
mTargetContext = mInstrumentation.getTargetContext();
CtsAppTestUtils.turnScreenOn(mInstrumentation, mContext);
cleanupResiduals();
// Press home key to ensure stopAppSwitches is called so the grace period of
// the background start will be ignored if there's any.
UiDevice.getInstance(mInstrumentation).pressHome();
}
@After
public void tearDown() throws Exception {
cleanupResiduals();
}
private void cleanupResiduals() {
// Stop all the packages to avoid residual impact
for (int i = 0; i < PACKAGE_NAMES.length; i++) {
final String pkgName = PACKAGE_NAMES[i];
SystemUtil.runWithShellPermissionIdentity(() -> {
mActivityManager.forceStopPackage(pkgName);
});
}
// Make sure we are in Home screen
mInstrumentation.getUiAutomation().performGlobalAction(
AccessibilityService.GLOBAL_ACTION_HOME);
}
private void prepareProcess(WatchUidRunner uidWatcher) throws Exception {
// Bypass bg-service-start restriction.
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"dumpsys deviceidle whitelist +" + PACKAGE_NAME_APP1);
// start background service.
Bundle extras = LocalForegroundService.newCommand(
LocalForegroundService.COMMAND_START_NO_FOREGROUND);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, extras);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"dumpsys deviceidle whitelist -" + PACKAGE_NAME_APP1);
}
@Test
public void testFgsDelegate() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
String[] dumpLines;
try {
prepareProcess(uidWatcher);
setForegroundServiceDelegate(PACKAGE_NAME_APP1, true);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, DUMP_COMMAND).split("\n");
assertNotNull(CtsAppTestUtils.findLine(dumpLines, "isForeground=true"));
assertNotNull(CtsAppTestUtils.findLine(dumpLines, "isFgsDelegate=true"));
setForegroundServiceDelegate(PACKAGE_NAME_APP1, false);
// The delegated foreground service is stopped, go back to background service state.
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, DUMP_COMMAND).split("\n");
assertNull(CtsAppTestUtils.findLine(dumpLines, "isForeground=true"));
assertNull(CtsAppTestUtils.findLine(dumpLines, "isFgsDelegate=true"));
// Start delegated foreground service again, the app goes to FGS state.
setForegroundServiceDelegate(PACKAGE_NAME_APP1, true);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, DUMP_COMMAND).split("\n");
assertNotNull(CtsAppTestUtils.findLine(dumpLines, "isForeground=true"));
assertNotNull(CtsAppTestUtils.findLine(dumpLines, "isFgsDelegate=true"));
// Stop foreground service delegate again, the app goes to background service state.
setForegroundServiceDelegate(PACKAGE_NAME_APP1, false);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, DUMP_COMMAND).split("\n");
assertNull(CtsAppTestUtils.findLine(dumpLines, "isForeground=true"));
assertNull(CtsAppTestUtils.findLine(dumpLines, "isFgsDelegate=true"));
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uidWatcher.finish();
}
}
@Test
public void testFgsDelegateNotAllowedWhenAppCanNotStartFGS() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
String[] dumpLines;
try {
prepareProcess(uidWatcher);
// Disallow app1 to start FGS.
allowBgFgsStart(PACKAGE_NAME_APP1, false);
// app1 is in the background, because it can not start FGS from the background, it is
// also not allowed to start FGS delegate.
setForegroundServiceDelegate(PACKAGE_NAME_APP1, true);
try {
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Allow app1 to start FGS.
allowBgFgsStart(PACKAGE_NAME_APP1, true);
// Now it can start FGS delegate.
setForegroundServiceDelegate(PACKAGE_NAME_APP1, true);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, DUMP_COMMAND).split("\n");
assertNotNull(CtsAppTestUtils.findLine(dumpLines, "isForeground=true"));
assertNotNull(CtsAppTestUtils.findLine(dumpLines, "isFgsDelegate=true"));
// Stop FGS delegate.
setForegroundServiceDelegate(PACKAGE_NAME_APP1, false);
// The delegated foreground service is stopped, go back to background service state.
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, DUMP_COMMAND).split("\n");
assertNull(CtsAppTestUtils.findLine(dumpLines, "isForeground=true"));
assertNull(CtsAppTestUtils.findLine(dumpLines, "isFgsDelegate=true"));
// Stop the background service.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uidWatcher.finish();
}
}
@Test
public void testFgsDelegateAfterForceStopPackage() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
String[] dumpLines;
try {
prepareProcess(uidWatcher);
setForegroundServiceDelegate(PACKAGE_NAME_APP1, true);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, DUMP_COMMAND).split("\n");
assertNotNull(CtsAppTestUtils.findLine(dumpLines, "isForeground=true"));
assertNotNull(CtsAppTestUtils.findLine(dumpLines, "isFgsDelegate=true"));
SystemUtil.runWithShellPermissionIdentity(() -> {
mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
});
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, DUMP_COMMAND).split("\n");
assertNull(CtsAppTestUtils.findLine(dumpLines, "isForeground=true"));
assertNull(CtsAppTestUtils.findLine(dumpLines, "isFgsDelegate=true"));
} finally {
uidWatcher.finish();
}
}
private void setForegroundServiceDelegate(String packageName, boolean isStart)
throws Exception {
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"am set-foreground-service-delegate --user "
+ UserHandle.getUserId(android.os.Process.myUid())
+ " " + packageName
+ (isStart ? " start" : " stop"));
}
/**
* SYSTEM_ALERT_WINDOW permission will allow both BG-activity start and BG-FGS start.
* Some cases we want to grant this permission to allow FGS start from the background.
* Some cases we want to revoke this permission to disallow FGS start from the background..
*
* Note: by default the testing apps have SYSTEM_ALERT_WINDOW permission in manifest file.
*/
private void allowBgFgsStart(String packageName, boolean allow) throws Exception {
if (allow) {
PermissionUtils.grantPermission(
packageName, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
} else {
PermissionUtils.revokePermission(
packageName, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
}
}
}