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);
+                }
+        );
+    }
 }