blob: 0f98cdf3ad2026fd992e96f2db0df4299c9174e2 [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NETWORK;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.stubs.LocalForegroundService.ACTION_START_FGS_RESULT;
import static android.app.stubs.LocalForegroundServiceLocation.ACTION_START_FGSL_RESULT;
import static android.os.PowerExemptionManager.REASON_PUSH_MESSAGING;
import static android.os.PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
import static android.os.PowerExemptionManager.REASON_UNKNOWN;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_NONE;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import android.accessibilityservice.AccessibilityService;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.ForegroundServiceStartNotAllowedException;
import android.app.Instrumentation;
import android.app.cts.android.app.cts.tools.WaitForBroadcast;
import android.app.cts.android.app.cts.tools.WatchUidRunner;
import android.app.stubs.CommandReceiver;
import android.app.stubs.LocalForegroundService;
import android.app.stubs.LocalForegroundServiceLocation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerExemptionManager;
import android.os.SystemClock;
import android.permission.cts.PermissionUtils;
import android.platform.test.annotations.AsbSecurityTest;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ActivityManagerFgsBgStartTest {
private static final String TAG = ActivityManagerFgsBgStartTest.class.getName();
static final String STUB_PACKAGE_NAME = "android.app.stubs";
static final String PACKAGE_NAME_APP1 = "com.android.app1";
static final String PACKAGE_NAME_APP2 = "com.android.app2";
static final String PACKAGE_NAME_APP3 = "com.android.app3";
private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
"default_fgs_starts_restriction_enabled";
private static final String KEY_FGS_START_FOREGROUND_TIMEOUT =
"fgs_start_foreground_timeout";
private static final String KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR =
"push_messaging_over_quota_behavior";
private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
public static final Integer LOCAL_SERVICE_PROCESS_CAPABILITY = new Integer(
PROCESS_CAPABILITY_FOREGROUND_CAMERA
| PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
| PROCESS_CAPABILITY_NETWORK);
static final int WAITFOR_MSEC = 10000;
private static final int TEMP_ALLOWLIST_DURATION_MS = 2000;
private static final String[] PACKAGE_NAMES = {
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, PACKAGE_NAME_APP3
};
private Context mContext;
private Instrumentation mInstrumentation;
private Context mTargetContext;
private int mOrigDeviceDemoMode = 0;
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mContext = mInstrumentation.getContext();
mTargetContext = mInstrumentation.getTargetContext();
for (int i = 0; i < PACKAGE_NAMES.length; ++i) {
CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAMES[i]);
// The manifest file gives test app SYSTEM_ALERT_WINDOW permissions, which also exempt
// the app from BG-FGS-launch restriction. Remove SYSTEM_ALERT_WINDOW permission to test
// other BG-FGS-launch exemptions.
allowBgActivityStart(PACKAGE_NAMES[i], false);
}
CtsAppTestUtils.turnScreenOn(mInstrumentation, mContext);
cleanupResiduals();
enableFgsRestriction(true, true, null);
// 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 {
for (int i = 0; i < PACKAGE_NAMES.length; ++i) {
CtsAppTestUtils.makeUidIdle(mInstrumentation, PACKAGE_NAMES[i]);
allowBgActivityStart(PACKAGE_NAMES[i], true);
}
cleanupResiduals();
enableFgsRestriction(true, true, null);
for (String packageName: PACKAGE_NAMES) {
resetFgsRestriction(packageName);
}
}
private void cleanupResiduals() {
// Stop all the packages to avoid residual impact
final ActivityManager am = mContext.getSystemService(ActivityManager.class);
for (int i = 0; i < PACKAGE_NAMES.length; i++) {
final String pkgName = PACKAGE_NAMES[i];
SystemUtil.runWithShellPermissionIdentity(() -> {
am.forceStopPackage(pkgName);
});
}
// Make sure we are in Home screen
mInstrumentation.getUiAutomation().performGlobalAction(
AccessibilityService.GLOBAL_ACTION_HOME);
}
/**
* APP1 is in BG state, it can start FGSL, but it won't get location capability.
* APP1 is in TOP state, it gets location capability.
* @throws Exception
*/
@Test
public void testFgsLocationStartFromBG() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in BG state, Start FGSL in APP1, it won't get location capability.
Bundle bundle = new Bundle();
bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
// start FGSL.
enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
// APP1 is in FGS state, but won't get location capability.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NETWORK));
waiter.doWait(WAITFOR_MSEC);
// stop FGSL
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// APP1 is in FGS state,
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
// start FGSL in app1, it won't get location capability.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
// APP1 is in STATE_FG_SERVICE, but won't get location capability.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NETWORK));
waiter.doWait(WAITFOR_MSEC);
// stop FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// stop FGSL.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// Put APP1 in TOP state, now it gets location capability (because the TOP process
// gets all while-in-use permission (not from FGSL).
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in TOP state, start the FGSL in APP1, it will get location capability.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
// Stop the activity.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// The FGSL still has location capability because it is started from TOP.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_ALL));
waiter.doWait(WAITFOR_MSEC);
// Stop FGSL.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
} finally {
uid1Watcher.finish();
}
}
/**
* APP1 is in BG state, it can start FGSL in APP2, but the FGS won't get location
* capability.
* APP1 is in TOP state, it can start FGSL in APP2, FGSL gets location capability.
* @throws Exception
*/
@Test
public void testFgsLocationStartFromBGTwoProcesses() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
try {
// APP1 is in BG state, start FGSL in APP2.
Bundle bundle = new Bundle();
bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, bundle);
// APP2 won't have location capability because APP1 is not in TOP state.
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NETWORK));
waiter.doWait(WAITFOR_MSEC);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
// Put APP1 in TOP state
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
// From APP1, start FGSL in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, bundle);
// Now APP2 gets location capability.
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_ALL));
waiter.doWait(WAITFOR_MSEC);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
}
}
/**
* APP1 is in BG state, by a PendingIntent, it can start FGSL in APP2,
* but the FGS won't get location capability.
* APP1 is in TOP state, by a PendingIntent, it can start FGSL in APP2,
* FGSL gets location capability.
* @throws Exception
*/
@Test
public void testFgsLocationPendingIntent() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
try {
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in BG state, start FGSL in APP2.
enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_SEND_FGSL_PENDING_INTENT,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
// APP2 won't have location capability.
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NETWORK));
waiter.doWait(WAITFOR_MSEC);
// Stop FGSL in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Put APP1 in FGS state, start FGSL in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NETWORK));
waiter.doWait(WAITFOR_MSEC);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_SEND_FGSL_PENDING_INTENT,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
// APP2 won't have location capability.
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NETWORK));
waiter.doWait(WAITFOR_MSEC);
// stop FGSL in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
// put APP1 in TOP state, start FGSL in APP2.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_CREATE_FGSL_PENDING_INTENT,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_SEND_FGSL_PENDING_INTENT,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
// APP2 now have location capability (because APP1 is TOP)
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_ALL));
waiter.doWait(WAITFOR_MSEC);
// stop FGSL in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
// stop FGS in APP1,
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// stop TOP activity in APP1.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
}
}
/**
* Test a FGS start by bind from BG does not get get while-in-use capability.
* @throws Exception
*/
@Test
@AsbSecurityTest(cveBugId = 173516292)
public void testFgsLocationStartFromBGWithBind() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in BG state, bind FGSL in APP1 first.
enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
Bundle bundle = new Bundle();
bundle.putInt(LocalForegroundServiceLocation.EXTRA_FOREGROUND_SERVICE_TYPE,
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
// Then start FGSL in APP1, it won't get location capability.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
// APP1 is in FGS state, but won't get location capability.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE,
new Integer(PROCESS_CAPABILITY_NETWORK));
waiter.doWait(WAITFOR_MSEC);
// unbind service.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// stop FGSL
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
} finally {
uid1Watcher.finish();
}
}
@Test
public void testUpdateUidProcState() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP3, 0);
WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
WAITFOR_MSEC);
try {
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
enableFgsRestriction(false, true, null);
// START FGS in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
// APP2 proc state is 4.
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// APP2 binds to APP1.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP1, Context.BIND_INCLUDE_CAPABILITIES, null);
// APP1 gets proc state 4.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE);
// Start activity in APP3, this put APP3 in TOP state.
allowBgActivityStart(PACKAGE_NAME_APP3, true);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP3, PACKAGE_NAME_APP3, 0, null);
// APP3 gets proc state 2.
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// APP3 repeatedly bind/unbind with APP2, observer APP1 proc state change.
// Observe updateUidProcState() call latency.
for (int i = 0; i < 10; ++i) {
// APP3 bind to APP2
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_BIND_SERVICE,
PACKAGE_NAME_APP3, PACKAGE_NAME_APP2, Context.BIND_INCLUDE_CAPABILITIES,
null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
PACKAGE_NAME_APP3, PACKAGE_NAME_APP2, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
}
// unbind service.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP3, PACKAGE_NAME_APP3, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
PACKAGE_NAME_APP3, PACKAGE_NAME_APP2, 0, null);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP1, 0, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
allowBgActivityStart(PACKAGE_NAME_APP3, false);
}
}
/**
* Test FGS background startForeground() restriction, use DeviceConfig to turn on restriction.
* @throws Exception
*/
@Test
public void testFgsStartFromBG1() throws Exception {
testFgsStartFromBG(true);
}
/**
* Test FGS background startForeground() restriction, use AppCompat CHANGE ID to turn on
* restriction.
* @throws Exception
*/
@Test
public void testFgsStartFromBG2() throws Exception {
testFgsStartFromBG(false);
}
private void testFgsStartFromBG(boolean useDeviceConfig) throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// disable the FGS background startForeground() restriction.
enableFgsRestriction(false, true, null);
enableFgsRestriction(false, useDeviceConfig, PACKAGE_NAME_APP1);
// APP1 is in BG state, Start FGS in APP1.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 is in STATE_FG_SERVICE.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// stop FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Enable the FGS background startForeground() restriction.
allowBgActivityStart(PACKAGE_NAME_APP1, false);
enableFgsRestriction(true, true, null);
enableFgsRestriction(true, useDeviceConfig, PACKAGE_NAME_APP1);
// Start FGS in BG state.
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Put APP1 in TOP state.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
allowBgActivityStart(PACKAGE_NAME_APP1, false);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// Stop activity.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// FGS is still running.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
}
}
/**
* Test a FGS can start from a process that is at BOUND_TOP state.
* @throws Exception
*/
@Test
public void testFgsStartFromBoundTopState() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP3, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Put APP1 in TOP state.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// APP1 bound to service in APP2, APP2 get BOUND_TOP state.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_BIND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP);
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// APP2 can start FGS in APP3.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop activity.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// unbind service.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_UNBIND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
}
}
/**
* Test a FGS can start from a process that is at FOREGROUND_SERVICE state.
* @throws Exception
*/
@Test
public void testFgsStartFromFgsState() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP3, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Put APP1 in TOP state.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// APP1 can start FGS in APP2, APP2 gets FOREGROUND_SERVICE state.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// APP2 can start FGS in APP3.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop activity in APP1.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Stop FGS in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Stop FGS in APP3.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP3, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
}
}
/**
* When the service is started by bindService() command, test when BG-FGS-launch
* restriction is disabled, FGS can start from background.
* @throws Exception
*/
@Test
public void testFgsStartFromBGWithBind() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGSL_RESULT);
// APP1 is in BG state, bind FGSL in APP1 first.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// Then start FGSL in APP1
enableFgsRestriction(false, true, null);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 is in FGS state
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// stop FGS
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// unbind service.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
}
}
/**
* When the service is started by bindService() command, test when BG-FGS-launch
* restriction is enabled, FGS can NOT start from background.
* @throws Exception
*/
@Test
public void testFgsStartFromBGWithBindWithRestriction() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
enableFgsRestriction(true, true, null);
// APP1 is in BG state, bind FGSL in APP1 first.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// Then start FGS in APP1
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// stop FGS
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// unbind service.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
}
}
/**
* Test BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS flag.
* Shell has START_ACTIVITIES_FROM_BACKGROUND permission, it can use this bind flag to
* pass BG-Activity-launch ability to APP2, then APP2 can start APP2 FGS from background.
*/
@Test
public void testFgsBindingFlagActivity() throws Exception {
testFgsBindingFlag(Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS);
}
/**
* Test BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND flag.
* Shell has START_FOREGROUND_SERVICES_FROM_BACKGROUND permission, it can use this bind flag to
* pass BG-FGS-launch ability to APP2, then APP2 can start APP3 FGS from background.
*/
@Test
public void testFgsBindingFlagFGS() throws Exception {
testFgsBindingFlag(Context.BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND);
}
/**
* Test no binding flag.
* Shell has START_FOREGROUND_SERVICES_FROM_BACKGROUND permission, without any bind flag,
* the BG-FGS-launch ability can be passed to APP2 by service binding, then APP2 can start
* APP3 FGS from background.
*/
@Test
public void testFgsBindingFlagNone() throws Exception {
testFgsBindingFlag(0);
}
private void testFgsBindingFlag(int bindingFlag) throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP3, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// testapp is in background.
// testapp binds to service in APP2, APP2 still in background state.
final Intent intent = new Intent().setClassName(
PACKAGE_NAME_APP2, "android.app.stubs.LocalService");
/*
final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
runWithShellPermissionIdentity(() -> {
mTargetContext.bindService(intent, connection,
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
});
// APP2 can not start FGS in APP3.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// testapp unbind service in APP2.
runWithShellPermissionIdentity(() -> mTargetContext.unbindService(connection));
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
*/
// testapp is in background.
// testapp binds to service in APP2 using the binding flag.
// APP2 still in background state.
final ServiceConnection connection2 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
runWithShellPermissionIdentity(() -> mTargetContext.bindService(intent, connection2,
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
| bindingFlag));
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Because the binding flag,
// APP2 can start FGS from background.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// testapp unbind service in APP2.
runWithShellPermissionIdentity(() -> mTargetContext.unbindService(connection2));
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
// Stop the FGS in APP3.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP3, PACKAGE_NAME_APP3, 0, null);
uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
uid3Watcher.finish();
}
}
/**
* Test a FGS can start from BG if the app has SYSTEM_ALERT_WINDOW permission.
*/
@Test
public void testFgsStartSystemAlertWindow() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
PermissionUtils.grantPermission(
PACKAGE_NAME_APP1, android.Manifest.permission.SYSTEM_ALERT_WINDOW);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
}
}
/**
* Test a FGS can start from BG if the device is in retail demo mode.
*/
@Test
// Change Settings.Global.DEVICE_DEMO_MODE on device may trigger other listener and put
// the device in undesired state, for example, the battery charge level is set to 35%
// permanently, ignore this test for now.
@Ignore
public void testFgsStartRetailDemoMode() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
runWithShellPermissionIdentity(()-> {
mOrigDeviceDemoMode = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_DEMO_MODE, 0); });
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
runWithShellPermissionIdentity(()-> {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_DEMO_MODE, 1); });
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
runWithShellPermissionIdentity(()-> {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_DEMO_MODE, mOrigDeviceDemoMode); });
}
}
// At Context.startForegroundService() or Service.startForeground() calls, if the FGS is
// restricted by background restriction and the app's targetSdkVersion is at least S, the
// framework throws a ForegroundServiceStartNotAllowedException with error message.
@Test
@Ignore("The instrumentation is allowed to star FGS, it does not throw the exception")
public void testFgsStartFromBGException() throws Exception {
ForegroundServiceStartNotAllowedException expectedException = null;
final Intent intent = new Intent().setClassName(
PACKAGE_NAME_APP1, "android.app.stubs.LocalForegroundService");
try {
allowBgActivityStart("android.app.stubs", false);
enableFgsRestriction(true, true, null);
mContext.startForegroundService(intent);
} catch (ForegroundServiceStartNotAllowedException e) {
expectedException = e;
} finally {
mContext.stopService(intent);
allowBgActivityStart("android.app.stubs", true);
}
String expectedMessage = "mAllowStartForeground false";
assertNotNull(expectedException);
assertTrue(expectedException.getMessage().contains(expectedMessage));
}
/**
* Test a FGS can start from BG if the app is in the DeviceIdleController's AllowList.
*/
@Test
public void testFgsStartAllowList() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Add package to AllowList.
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"dumpsys deviceidle whitelist +" + PACKAGE_NAME_APP1);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
// Remove package from AllowList.
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"dumpsys deviceidle whitelist -" + PACKAGE_NAME_APP1);
}
}
/**
* Test temp allowlist types in BroadcastOptions.
*/
@Test
public void testTempAllowListType() throws Exception {
testTempAllowListTypeInternal(TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED);
testTempAllowListTypeInternal(TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED);
}
private void testTempAllowListTypeInternal(int type) throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Now it can start FGS.
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
runWithShellPermissionIdentity(()-> {
final BroadcastOptions options = BroadcastOptions.makeBasic();
// setTemporaryAppAllowlist API requires
// START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
options.setTemporaryAppAllowlist(TEMP_ALLOWLIST_DURATION_MS, type, REASON_UNKNOWN,
"");
// Must use Shell to issue this command because Shell has
// START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
CommandReceiver.sendCommandWithBroadcastOptions(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
options.toBundle());
});
if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY);
} else if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED) {
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
}
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
// Sleep 10 seconds to let the temp allowlist expire so it won't affect next test case.
SystemClock.sleep(TEMP_ALLOWLIST_DURATION_MS);
}
}
/**
* Test a FGS can start from BG if the process had a visible activity recently.
*/
@LargeTest
@Test
public void testVisibleActivityGracePeriod() throws Exception {
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
final String namespaceActivityManager = "activity_manager";
final String keyFgToBgFgsGraceDuration = "fg_to_bg_fgs_grace_duration";
final long[] curFgToBgFgsGraceDuration = {-1};
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Allow bg actvity start from APP1.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
SystemUtil.runWithShellPermissionIdentity(() -> {
curFgToBgFgsGraceDuration[0] = DeviceConfig.getInt(
namespaceActivityManager,
keyFgToBgFgsGraceDuration, -1);
DeviceConfig.setProperty(namespaceActivityManager,
keyFgToBgFgsGraceDuration,
Long.toString(WAITFOR_MSEC), false);
});
testVisibleActivityGracePeriodInternal(uid2Watcher, "KEYCODE_HOME");
testVisibleActivityGracePeriodInternal(uid2Watcher, "KEYCODE_BACK");
} finally {
uid2Watcher.finish();
// Remove package from AllowList.
allowBgActivityStart(PACKAGE_NAME_APP1, false);
if (curFgToBgFgsGraceDuration[0] >= 0) {
SystemUtil.runWithShellPermissionIdentity(() -> {
DeviceConfig.setProperty(namespaceActivityManager,
keyFgToBgFgsGraceDuration,
Long.toString(curFgToBgFgsGraceDuration[0]), false);
});
} else {
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"device_config delete " + namespaceActivityManager
+ " " + keyFgToBgFgsGraceDuration);
}
}
}
private void testVisibleActivityGracePeriodInternal(WatchUidRunner uidWatcher, String keyCode)
throws Exception {
testVisibleActivityGracePeriodInternal(uidWatcher, keyCode, null,
() -> uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE), true);
testVisibleActivityGracePeriodInternal(uidWatcher, keyCode,
() -> SystemClock.sleep(WAITFOR_MSEC + 2000), // Wait for the grace period to expire
() -> {
try {
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE);
fail("Service should not enter foreground service state");
} catch (Exception e) {
// Expected.
}
}, false);
}
private void testVisibleActivityGracePeriodInternal(WatchUidRunner uidWatcher,
String keyCode, Runnable prep, Runnable verifier, boolean stopFgs) throws Exception {
// Put APP2 in TOP state.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
// Take a nap to wait for the UI to settle down.
SystemClock.sleep(2000);
// Now inject key event.
CtsAppTestUtils.executeShellCmd(mInstrumentation, "input keyevent " + keyCode);
// It should go to the cached state.
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
if (prep != null) {
prep.run();
}
// Start FGS from APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
if (verifier != null) {
verifier.run();
}
if (stopFgs) {
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
}
}
/**
* After background service is started, after 10 seconds timeout, the startForeground() can
* succeed or not depends on the service's app proc state.
* Test starService() -> startForeground()
*/
@Test
public void testStartForegroundTimeout() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
setFgsStartForegroundTimeout(DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS);
// Put app to a TOP proc state.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
allowBgActivityStart(PACKAGE_NAME_APP1, false);
// 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);
// stop the activity.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE);
// Sleep after the timeout DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS
SystemClock.sleep(DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS + 1000);
extras = LocalForegroundService.newCommand(
LocalForegroundService.COMMAND_START_FOREGROUND);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, extras);
// APP1 does not enter FGS state
// startForeground() is called after 10 seconds FgsStartForegroundTimeout.
try {
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Put app to a TOP proc state.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_TOP, new Integer(PROCESS_CAPABILITY_ALL));
allowBgActivityStart(PACKAGE_NAME_APP1, false);
// Call startForeground().
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
extras = LocalForegroundService.newCommand(
LocalForegroundService.COMMAND_START_FOREGROUND);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, extras);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
} finally {
uid1Watcher.finish();
setFgsStartForegroundTimeout(DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS);
}
}
/**
* After startForeground() and stopForeground(), the second startForeground() can succeed or not
* depends on the service's app proc state.
* Test startForegroundService() -> startForeground() -> stopForeground() -> startForeground()
* -> startForeground().
*/
@Test
public void testSecondStartForeground() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Bypass bg-service-start restriction.
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"dumpsys deviceidle whitelist +" + PACKAGE_NAME_APP1);
// Start foreground service from APP1, the service can enter FGS.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"dumpsys deviceidle whitelist -" + PACKAGE_NAME_APP1);
// stopForeground(), the service exits FGS, become a background service.
Bundle extras = LocalForegroundService.newCommand(
LocalForegroundService.COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, extras);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_SERVICE,
new Integer(PROCESS_CAPABILITY_NONE));
// APP2 is in the background, from APP2, call startForeground().
// When APP2 calls Context.startService(), setFgsRestrictionLocked() is called,
// because APP2 is in the background, mAllowStartForeground is set to false.
// When Service.startForeground() is called, setFgsRestrictionLocked() is called again,
// APP1's proc state is in the background and mAllowStartForeground is set to false.
extras = LocalForegroundService.newCommand(
LocalForegroundService.COMMAND_START_FOREGROUND);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP1, 0, extras);
try {
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Put APP1 to a TOP proc state.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
allowBgActivityStart(PACKAGE_NAME_APP1, false);
// APP2 is in the background, from APP2, call startForeground() second time.
// When APP2 calls Context.startService(), setFgsRestrictionLocked() is called,
// because APP2 is in the background, mAllowStartForeground is set to false.
// When Service.startForeground() is called, setFgsRestrictionLocked() is called again,
// because APP1's proc state is in the foreground and mAllowStartForeground is set to
// true.
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
extras = LocalForegroundService.newCommand(
LocalForegroundService.COMMAND_START_FOREGROUND);
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP1, 0, extras);
waiter.doWait(WAITFOR_MSEC);
// Stop app1's activity.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
LOCAL_SERVICE_PROCESS_CAPABILITY);
// Stop the FGS.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
} finally {
uid1Watcher.finish();
}
}
/**
* Test OP_ACTIVATE_VPN and OP_ACTIVATE_PLATFORM_VPN are exempted from BG-FGS-launch
* restriction.
* @throws Exception
*/
@Test
public void testFgsStartVpn() throws Exception {
testFgsStartVpnInternal("ACTIVATE_VPN");
testFgsStartVpnInternal("ACTIVATE_PLATFORM_VPN");
}
private void testFgsStartVpnInternal(String vpnAppOp) throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
setAppOp(PACKAGE_NAME_APP1, vpnAppOp, true);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"appops reset " + PACKAGE_NAME_APP1);
}
}
/**
* The default behavior for temp allowlist reasonCode REASON_PUSH_MESSAGING_OVER_QUOTA
* is TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED (not allowed to start FGS). But
* the behavior can be changed by device config command. There are three possible values:
* {@link TEMPORARY_ALLOW_LIST_TYPE_NONE} (-1):
* not temp allowlisted.
* {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} (0):
* temp allowlisted and allow FGS.
* {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} (1):
* temp allowlisted, not allow FGS.
* @throws Exception
*/
@Test
public void testPushMessagingOverQuota() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
final int defaultBehavior = getPushMessagingOverQuotaBehavior();
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Default behavior is TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED.
setPushMessagingOverQuotaBehavior(
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED);
// Start FGS in BG state.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
setPushMessagingOverQuotaBehavior(TEMPORARY_ALLOW_LIST_TYPE_NONE);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
runWithShellPermissionIdentity(() -> {
mContext.getSystemService(PowerExemptionManager.class).addToTemporaryAllowList(
PACKAGE_NAME_APP1, PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA,
"", TEMP_ALLOWLIST_DURATION_MS);
});
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Change behavior to TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED.
setPushMessagingOverQuotaBehavior(
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED);
runWithShellPermissionIdentity(() -> {
mContext.getSystemService(PowerExemptionManager.class).addToTemporaryAllowList(
PACKAGE_NAME_APP1, PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA,
"", TEMP_ALLOWLIST_DURATION_MS);
});
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
// Change back to default behavior.
setPushMessagingOverQuotaBehavior(defaultBehavior);
// allow temp allowlist to expire.
SystemClock.sleep(TEMP_ALLOWLIST_DURATION_MS);
}
}
/**
* Test temp allowlist reasonCode in BroadcastOptions.
* When REASON_PUSH_MESSAGING_OVER_QUOTA, DeviceIdleController changes temp allowlist type to
* TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED so FGS start is not allowed.
* When REASON_DENIED (-1), DeviceIdleController changes temp allowlist type to
* TEMPORARY_ALLOWLIST_TYPE_NONE, the temp allowlist itself is not allowed.
* All other reason codes, DeviceIdleController does not change temp allowlist type.
*/
@Test
public void testTempAllowListReasonCode() throws Exception {
// FGS start is temp allowed.
testTempAllowListReasonCodeInternal(REASON_PUSH_MESSAGING);
// FGS start is not allowed.
testTempAllowListReasonCodeInternal(REASON_PUSH_MESSAGING_OVER_QUOTA);
// Temp allowlist itself is not allowed. REASON_DENIED is not exposed in
// PowerExemptionManager, just use its value "-1" here.
testTempAllowListReasonCodeInternal(-1);
}
private void testTempAllowListReasonCodeInternal(int reasonCode) throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
final int defaultBehavior = getPushMessagingOverQuotaBehavior();
try {
setPushMessagingOverQuotaBehavior(
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED);
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Now it can start FGS.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
runWithShellPermissionIdentity(()-> {
final BroadcastOptions options = BroadcastOptions.makeBasic();
// setTemporaryAppAllowlist API requires
// START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
options.setTemporaryAppAllowlist(TEMP_ALLOWLIST_DURATION_MS,
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, reasonCode,
"");
// Must use Shell to issue this command because Shell has
// START_FOREGROUND_SERVICES_FROM_BACKGROUND permission.
CommandReceiver.sendCommandWithBroadcastOptions(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
options.toBundle());
});
if (reasonCode == REASON_PUSH_MESSAGING) {
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY);
} else if (reasonCode == REASON_PUSH_MESSAGING_OVER_QUOTA) {
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
}
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
setPushMessagingOverQuotaBehavior(defaultBehavior);
// Sleep to let the temp allowlist expire so it won't affect next test case.
SystemClock.sleep(TEMP_ALLOWLIST_DURATION_MS);
}
}
/**
* Test default_input_method is exempted from BG-FGS-start restriction.
* @throws Exception
*/
@Test
public void testFgsStartInputMethod() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
final String defaultInputMethod = CtsAppTestUtils.executeShellCmd(mInstrumentation,
"settings get --user current secure default_input_method");
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
// Change default_input_method to PACKAGE_NAME_APP1.
final ComponentName cn = new ComponentName(PACKAGE_NAME_APP1, "xxx");
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"settings put --user current secure default_input_method "
+ cn.flattenToShortString());
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"settings put --user current secure default_input_method "
+ defaultInputMethod);
}
}
@Test
public void testFgsStartInBackgroundRestrictions() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP2, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
WAITFOR_MSEC);
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
final String dumpCommand = "dumpsys activity services " + PACKAGE_NAME_APP2
+ "/android.app.stubs.LocalForegroundService";
final long shortWaitMsec = 5_000;
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Set background restriction for APP2
setAppOp(PACKAGE_NAME_APP2, "RUN_ANY_IN_BACKGROUND", false);
// Start the APP1 into the TOP state.
allowBgActivityStart(PACKAGE_NAME_APP1, true);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_TOP);
// APP1 binds to APP2.
CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, Context.BIND_INCLUDE_CAPABILITIES, null);
// APP2 gets proc state BOUND_TOP.
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_BOUND_TOP);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// START FGS in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
waiter.doWait(WAITFOR_MSEC);
SystemClock.sleep(shortWaitMsec);
String[] dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, dumpCommand).split("\n");
assertNotNull(findLine(dumpLines, "isForeground=true"));
// Finish the activity in APP1
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_ACTIVITY,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
mInstrumentation.getUiAutomation().performGlobalAction(
AccessibilityService.GLOBAL_ACTION_HOME);
// APP1 should have been cached state now.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_CACHED_EMPTY);
// Th FGS in APP2 should have been normal service state now.
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_SERVICE);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// START FGS in APP1
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// APP2 should be in FGS state too now.
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_FG_SERVICE);
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// START FGS in APP2.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
waiter.doWait(WAITFOR_MSEC);
SystemClock.sleep(shortWaitMsec);
dumpLines = CtsAppTestUtils.executeShellCmd(
mInstrumentation, dumpCommand).split("\n");
assertNotNull(findLine(dumpLines, "isForeground=true"));
// Set background restriction for APP1.
setAppOp(PACKAGE_NAME_APP1, "RUN_ANY_IN_BACKGROUND", false);
// Both of them should have normal service state now.
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_SERVICE);
uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
WatchUidRunner.STATE_SERVICE);
} finally {
uid1Watcher.finish();
uid2Watcher.finish();
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"appops reset " + PACKAGE_NAME_APP1);
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"appops reset " + PACKAGE_NAME_APP2);
}
}
/**
* Find a line containing {@code label} in {@code lines}.
*/
private String findLine(String[] lines, CharSequence label) {
for (String line: lines) {
if (line.contains(label)) {
return line;
}
}
return null;
}
/**
* When PowerExemptionManager.addToTemporaryAllowList() is called more than one time, the second
* call can extend the duration of the first call if the first call has not expired yet.
* @throws Exception
*/
@Test
public void testOverlappedTempAllowList() throws Exception {
ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
PACKAGE_NAME_APP1, 0);
WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
WAITFOR_MSEC);
try {
// Enable the FGS background startForeground() restriction.
enableFgsRestriction(true, true, null);
// Start FGS in BG state.
WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
// APP1 does not enter FGS state
try {
waiter.doWait(WAITFOR_MSEC);
fail("Service should not enter foreground service state");
} catch (Exception e) {
}
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
runWithShellPermissionIdentity(() -> {
mContext.getSystemService(PowerExemptionManager.class).addToTemporaryAllowList(
PACKAGE_NAME_APP1, PowerExemptionManager.REASON_PUSH_MESSAGING,
"", 10000);
});
SystemClock.sleep(5000);
runWithShellPermissionIdentity(() -> {
mContext.getSystemService(PowerExemptionManager.class).addToTemporaryAllowList(
PACKAGE_NAME_APP1, PowerExemptionManager.REASON_PUSH_MESSAGING,
"", 10000);
});
SystemClock.sleep(5000);
// The first addToTemporaryAllowList()'s 10000ms duration has expired.
// Now FGS start is allowed by second addToTemporaryAllowList()'s 10000ms duration.
waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
waiter.prepare(ACTION_START_FGS_RESULT);
// Now it can start FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
waiter.doWait(WAITFOR_MSEC);
// Stop the FGS.
CommandReceiver.sendCommand(mContext,
CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
} finally {
uid1Watcher.finish();
// allow temp allowlist to expire.
SystemClock.sleep(5000);
}
}
/**
* Turn on the FGS BG-launch restriction. DeviceConfig can turn on restriction on the whole
* device (across all apps). AppCompat can turn on restriction on a single app package.
* @param enable true to turn on restriction, false to turn off.
* @param useDeviceConfig true to use DeviceConfig, false to use AppCompat CHANGE ID.
* @param packageName the packageName if using AppCompat CHANGE ID.
* @throws Exception
*/
private void enableFgsRestriction(boolean enable, boolean useDeviceConfig, String packageName)
throws Exception {
if (useDeviceConfig) {
runWithShellPermissionIdentity(() -> {
DeviceConfig.setProperty("activity_manager",
KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED,
Boolean.toString(enable), false);
}
);
} else {
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"am compat " + (enable ? "enable" : "disable")
+ " FGS_BG_START_RESTRICTION_CHANGE_ID " + packageName);
}
}
/**
* Clean up the FGS BG-launch restriction.
* @param packageName the packageName that will have its changeid override reset.
* @throws Exception
*/
private void resetFgsRestriction(String packageName)
throws Exception {
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"am compat reset FGS_BG_START_RESTRICTION_CHANGE_ID " + packageName);
}
/**
* SYSTEM_ALERT_WINDOW permission will allow both BG-activity start and BG-FGS start.
* Some cases we want to grant this permission to allow activity start to bring the app up to
* TOP state.
* Some cases we want to revoke this permission to test other BG-FGS-launch exemptions.
* @param packageName
* @param allow
* @throws Exception
*/
private void allowBgActivityStart(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);
}
}
private void setFgsStartForegroundTimeout(int timeoutMs) throws Exception {
runWithShellPermissionIdentity(() -> {
DeviceConfig.setProperty("activity_manager",
KEY_FGS_START_FOREGROUND_TIMEOUT,
Integer.toString(timeoutMs), false);
}
);
}
private void setAppOp(String packageName, String opStr, boolean allow) throws Exception {
CtsAppTestUtils.executeShellCmd(mInstrumentation,
"appops set " + packageName + " " + opStr + " "
+ (allow ? "allow" : "deny"));
}
private void setPushMessagingOverQuotaBehavior(
/* @PowerExemptionManager.TempAllowListType */ int type) throws Exception {
runWithShellPermissionIdentity(() -> {
DeviceConfig.setProperty("activity_manager",
KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR,
Integer.toString(type), false);
}
);
// Sleep 2 seconds to allow the device config change to be applied.
SystemClock.sleep(2000);
}
private int getPushMessagingOverQuotaBehavior() throws Exception {
final String defaultBehaviorStr = CtsAppTestUtils.executeShellCmd(mInstrumentation,
"device_config get activity_manager "
+ KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR).trim();
int defaultBehavior = TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
if (!defaultBehaviorStr.equals("null")) {
try {
defaultBehavior = Integer.parseInt(defaultBehaviorStr);
} catch (NumberFormatException e) {
Log.e("ActivityManagerFgsBgStartTest",
"getPushMessagingOverQuotaBehavior:", e);
}
}
return defaultBehavior;
}
}