Add test cases for background startForeground() improvement.
Add two test cases:
1. testStartForegroundTimeout() to test the 10 seconds timeout from
Context.startService() to first startForeground() call, the
startForeground() succeed or not depends on the service's app
proc state check.
2. testSecondStartForeground() to test
startForeground()/stopForeground(), then startForeground() again. The
second startForeground() succeed or not depends on the service's app
proc state check again.
Bug: 183204439
Test: atest cts/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest#testStartForegroundTimeout
Test: atest cts/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest#testSecondStartForeground
Change-Id: I98a163e0a4304eebee6226e887f2509f79e8ba65
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index 17e53a7..dd98b01 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -17,13 +17,13 @@
package android.app.stubs;
import android.app.ActivityManager;
+import android.app.ForegroundServiceStartNotAllowedException;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
@@ -55,6 +55,8 @@
public static final int COMMAND_START_CHILD_PROCESS = 15;
public static final int COMMAND_STOP_CHILD_PROCESS = 16;
public static final int COMMAND_WAIT_FOR_CHILD_PROCESS_GONE = 17;
+ public static final int COMMAND_START_SERVICE = 18;
+ public static final int COMMAND_STOP_SERVICE = 19;
public static final int RESULT_CHILD_PROCESS_STARTED = IBinder.FIRST_CALL_TRANSACTION;
public static final int RESULT_CHILD_PROCESS_STOPPED = IBinder.FIRST_CALL_TRANSACTION + 1;
@@ -108,14 +110,18 @@
case COMMAND_START_FOREGROUND_SERVICE:
doStartForegroundService(context, intent);
break;
+ case COMMAND_START_SERVICE:
+ doStartService(context, intent);
+ break;
case COMMAND_STOP_FOREGROUND_SERVICE:
- doStopForegroundService(context, intent, FG_SERVICE_NAME);
+ case COMMAND_STOP_SERVICE:
+ doStopService(context, intent, FG_SERVICE_NAME);
break;
case COMMAND_START_FOREGROUND_SERVICE_LOCATION:
doStartForegroundServiceWithType(context, intent);
break;
case COMMAND_STOP_FOREGROUND_SERVICE_LOCATION:
- doStopForegroundService(context, intent, FG_LOCATION_SERVICE_NAME);
+ doStopService(context, intent, FG_LOCATION_SERVICE_NAME);
break;
case COMMAND_START_ALERT_SERVICE:
doStartAlertService(context);
@@ -176,29 +182,39 @@
fgsIntent.putExtras(commandIntent);
fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME));
int command = LocalForegroundService.COMMAND_START_FOREGROUND;
- fgsIntent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
+ fgsIntent.putExtras(LocalForegroundService.newCommand(command));
try {
context.startForegroundService(fgsIntent);
- } catch (IllegalStateException e) {
- Log.d(TAG, "startForegroundService gets an IllegalStateException", e);
+ } catch (ForegroundServiceStartNotAllowedException e) {
+ Log.d(TAG, "startForegroundService gets an "
+ + " ForegroundServiceStartNotAllowedException", e);
}
}
+ private void doStartService(Context context, Intent commandIntent) {
+ String targetPackage = getTargetPackage(commandIntent);
+ Intent fgsIntent = new Intent();
+ fgsIntent.putExtras(commandIntent);
+ fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME));
+ context.startService(fgsIntent);
+ }
+
private void doStartForegroundServiceWithType(Context context, Intent commandIntent) {
String targetPackage = getTargetPackage(commandIntent);
Intent fgsIntent = new Intent();
fgsIntent.putExtras(commandIntent); // include the fg service type if any.
fgsIntent.setComponent(new ComponentName(targetPackage, FG_LOCATION_SERVICE_NAME));
int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
- fgsIntent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
+ fgsIntent.putExtras(LocalForegroundService.newCommand(command));
try {
context.startForegroundService(fgsIntent);
- } catch (IllegalStateException e) {
- Log.d(TAG, "startForegroundService gets an IllegalStateException", e);
+ } catch (ForegroundServiceStartNotAllowedException e) {
+ Log.d(TAG, "startForegroundService gets an "
+ + "ForegroundServiceStartNotAllowedException", e);
}
}
- private void doStopForegroundService(Context context, Intent commandIntent,
+ private void doStopService(Context context, Intent commandIntent,
String serviceName) {
String targetPackage = getTargetPackage(commandIntent);
Intent fgsIntent = new Intent();
@@ -246,7 +262,7 @@
final Intent intent = new Intent().setComponent(
new ComponentName(targetPackage, FG_LOCATION_SERVICE_NAME));
int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
- intent.putExtras(LocalForegroundService.newCommand(new Binder(), command));
+ intent.putExtras(LocalForegroundService.newCommand(command));
final PendingIntent pendingIntent = PendingIntent.getForegroundService(context, 0,
intent, PendingIntent.FLAG_IMMUTABLE);
sPendingIntent.put(targetPackage, pendingIntent);
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
index 25e77b0..77c607f 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundService.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -16,12 +16,14 @@
package android.app.stubs;
+import android.app.ForegroundServiceStartNotAllowedException;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -35,7 +37,7 @@
public class LocalForegroundService extends LocalService {
private static final String TAG = "LocalForegroundService";
- protected static final String EXTRA_COMMAND = "LocalForegroundService.command";
+ public static final String EXTRA_COMMAND = "LocalForegroundService.command";
public static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
public static String ACTION_START_FGS_RESULT =
"android.app.stubs.LocalForegroundService.RESULT";
@@ -90,7 +92,12 @@
.setSmallIcon(R.drawable.black)
.setShowForegroundImmediately(showNow)
.build();
- startForeground(mNotificationId, notification);
+ try {
+ startForeground(mNotificationId, notification);
+ } catch (ForegroundServiceStartNotAllowedException e) {
+ Log.d(TAG, "startForeground gets an "
+ + " ForegroundServiceStartNotAllowedException", e);
+ }
break;
}
case COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION:
@@ -137,6 +144,13 @@
return bundle;
}
+ public static Bundle newCommand(int command) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(LocalService.REPORT_OBJ_NAME, new IBinderParcelable(new Binder()));
+ bundle.putInt(EXTRA_COMMAND, command);
+ return bundle;
+ }
+
public static String getNotificationTitle(int id) {
return "I AM FOREGROOT #" + id;
}
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java b/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java
index 56346db..50c39d5 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java
@@ -16,6 +16,7 @@
package android.app.stubs;
+import android.app.ForegroundServiceStartNotAllowedException;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -63,9 +64,21 @@
.setContentTitle(getNotificationTitle(mNotificationId))
.setSmallIcon(R.drawable.black)
.build();
- startForeground(mNotificationId, notification);
+ try {
+ startForeground(mNotificationId, notification);
+ } catch (ForegroundServiceStartNotAllowedException e) {
+ Log.d(TAG, "startForeground gets an "
+ + " ForegroundServiceStartNotAllowedException", e);
+ }
//assertEquals(type, getForegroundServiceType());
break;
+ case COMMAND_STOP_FOREGROUND_REMOVE_NOTIFICATION:
+ Log.d(TAG, "Stopping foreground removing notification");
+ stopForeground(true);
+ break;
+ case COMMAND_START_NO_FOREGROUND:
+ Log.d(TAG, "Starting without calling startForeground()");
+ break;
default:
Log.e(TAG, "Unknown command: " + command);
}
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 3993cd2..624b38e 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -17,8 +17,12 @@
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.PowerWhitelistManager.REASON_UNKNOWN;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
@@ -37,6 +41,7 @@
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;
@@ -71,14 +76,20 @@
private static final String PACKAGE_NAME_APP1 = "com.android.app1";
private static final String PACKAGE_NAME_APP2 = "com.android.app2";
private static final String PACKAGE_NAME_APP3 = "com.android.app3";
- private static final String ACTION_START_FGS_RESULT =
- "android.app.stubs.LocalForegroundService.RESULT";
- private static final String ACTION_START_FGSL_RESULT =
- "android.app.stubs.LocalForegroundServiceLocation.RESULT";
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 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);
+
private static final int WAITFOR_MSEC = 10000;
private static final String[] PACKAGE_NAMES = {
@@ -1259,6 +1270,156 @@
}
/**
+ * 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);
+ Bundle extras = LocalForegroundService.newCommand(
+ LocalForegroundService.COMMAND_START_NO_FOREGROUND);
+ 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 background service.
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, extras);
+ CtsAppTestUtils.executeShellCmd(mInstrumentation,
+ "dumpsys deviceidle whitelist -" + PACKAGE_NAME_APP1);
+ // Sleep after the timeout DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS
+ SystemClock.sleep(DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS + 1000);
+
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, extras);
+ // APP1 does not enter FGS state
+ // startForeground() is called after 10 seconds FgsStartForegroundTimeout.
+ try {
+ waiter.doWait(WAITFOR_MSEC);
+ 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().
+ 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,
+ LOCAL_SERVICE_PROCESS_CAPABILITY);
+ 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().
+ */
+ @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.
+ 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()
+ 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));
+
+ // startForeground() again.
+ extras = LocalForegroundService.newCommand(
+ LocalForegroundService.COMMAND_START_FOREGROUND);
+ CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_START_SERVICE,
+ PACKAGE_NAME_APP1, 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 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() second time.
+ 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);
+ 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);
+ 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,
+ new Integer(PROCESS_CAPABILITY_NONE));
+ } finally {
+ uid1Watcher.finish();
+ }
+ }
+
+
+ /**
* 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.
@@ -1311,4 +1472,13 @@
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);
+ }
+ );
+ }
}