Test foreground service with START_STICKY flag.

1. Add LocalForegroundServiceSticky which is a sticky foreground service
(or not sticky depends on config).
2. For START_STICKY, when the process is killed by
"adb shell am crash" command, the service is restarted and Service.onStartCommand is
called with a null intent.
3. For START_NOT_STICKY, when process is killed, the service does not
restart and Service.onStartCommand is not called again.
4. For START_REDELIVER_INTENT, the service is restarted and
Service.onStartCommand is called with last intent.

Bug: 182832497
Test: atest CtsAppTestCases:android.app.cts.ActivityManagerProcessStateTest#testFgsSticky
Change-Id: If83179de2a96578c743104e9cfbf9f4257e19b10
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index a65329e..cd3fbdc 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -178,6 +178,14 @@
             </intent-filter>
         </service>
 
+        <service android:name="android.app.stubs.LocalForegroundServiceSticky"
+                 android:foregroundServiceType="camera|microphone"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.app.stubs.FOREGROUND_SERVICE"/>
+            </intent-filter>
+        </service>
+
         <service android:name="android.app.stubs.LocalGrantedService"
              android:permission="android.app.stubs.permission.TEST_GRANTED"
              android:exported="true">
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index dd98b01..b618dcc 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -57,6 +57,8 @@
     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 COMMAND_START_FOREGROUND_SERVICE_STICKY = 20;
+    public static final int COMMAND_STOP_FOREGROUND_SERVICE_STICKY = 21;
 
     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;
@@ -74,6 +76,8 @@
     public static final String FG_SERVICE_NAME = "android.app.stubs.LocalForegroundService";
     public static final String FG_LOCATION_SERVICE_NAME =
             "android.app.stubs.LocalForegroundServiceLocation";
+    public static final String FG_STICKY_SERVICE_NAME =
+            "android.app.stubs.LocalForegroundServiceSticky";
 
     public static final String ACTIVITY_NAME = "android.app.stubs.SimpleActivity";
 
@@ -123,6 +127,12 @@
             case COMMAND_STOP_FOREGROUND_SERVICE_LOCATION:
                 doStopService(context, intent, FG_LOCATION_SERVICE_NAME);
                 break;
+            case COMMAND_START_FOREGROUND_SERVICE_STICKY:
+                doStartForegroundServiceSticky(context, intent);
+                break;
+            case COMMAND_STOP_FOREGROUND_SERVICE_STICKY:
+                doStopService(context, intent, FG_STICKY_SERVICE_NAME);
+                break;
             case COMMAND_START_ALERT_SERVICE:
                 doStartAlertService(context);
                 break;
@@ -214,6 +224,21 @@
         }
     }
 
+    private void doStartForegroundServiceSticky(Context context, Intent commandIntent) {
+        String targetPackage = getTargetPackage(commandIntent);
+        Intent fgsIntent = new Intent();
+        fgsIntent.putExtras(commandIntent);
+        fgsIntent.setComponent(new ComponentName(targetPackage, FG_STICKY_SERVICE_NAME));
+        int command = LocalForegroundService.COMMAND_START_FOREGROUND;
+        fgsIntent.putExtras(LocalForegroundService.newCommand(command));
+        try {
+            context.startForegroundService(fgsIntent);
+        } catch (ForegroundServiceStartNotAllowedException e) {
+            Log.d(TAG, "startForegroundService gets an "
+                    + "ForegroundServiceStartNotAllowedException", e);
+        }
+    }
+
     private void doStopService(Context context, Intent commandIntent,
             String serviceName) {
         String targetPackage = getTargetPackage(commandIntent);
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
index 77c607f..83db042 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundService.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -67,7 +67,7 @@
     }
 
     @Override
-    public void onStart(Intent intent, int startId) {
+    public int onStartCommand(Intent intent, int flags, int startId) {
         String notificationChannelId = getNotificationChannelId();
         NotificationManager notificationManager = getSystemService(NotificationManager.class);
         notificationManager.createNotificationChannel(new NotificationChannel(
@@ -129,6 +129,7 @@
         // Do parent's onStart at the end, so we don't race with the test code waiting for us to
         // execute.
         super.onStart(intent, startId);
+        return START_NOT_STICKY;
     }
 
     @Override
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java b/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java
index 50c39d5..a0f140e 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundServiceLocation.java
@@ -45,7 +45,7 @@
     }
 
     @Override
-    public void onStart(Intent intent, int startId) {
+    public int onStartCommand(Intent intent, int flags, int startId) {
         String notificationChannelId = getNotificationChannelId();
         NotificationManager notificationManager = getSystemService(NotificationManager.class);
         notificationManager.createNotificationChannel(new NotificationChannel(
@@ -85,5 +85,6 @@
 
         sendBroadcast(new Intent(ACTION_START_FGSL_RESULT)
                 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND));
+        return START_NOT_STICKY;
     }
 }
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundServiceSticky.java b/tests/app/app/src/android/app/stubs/LocalForegroundServiceSticky.java
new file mode 100644
index 0000000..794ba7d
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundServiceSticky.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+/*
+ * 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.stubs;
+
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Foreground Service with location type.
+ */
+public class LocalForegroundServiceSticky extends LocalForegroundService {
+    private static final String TAG = "LocalForegroundServiceSticky";
+    public static String STICKY_FLAG =
+            "android.app.stubs.LocalForegroundServiceSticky.sticky_flag";
+    public static String ACTION_RESTART_FGS_STICKY_RESULT =
+            "android.app.stubs.LocalForegroundServiceSticky.STICKY_RESULT";
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent == null) {
+            Log.d(TAG, "LocalForegroundServiceSticky.onStartCommand: null intent");
+            sendBroadcast(new Intent(ACTION_RESTART_FGS_STICKY_RESULT).setFlags(
+                    Intent.FLAG_RECEIVER_FOREGROUND));
+            return START_STICKY;
+        } else {
+            super.onStartCommand(intent, flags, startId);
+            final int stickyFlag = intent.getIntExtra(STICKY_FLAG, START_NOT_STICKY);
+            Log.d(TAG, "LocalForegroundServiceSticky.onStartCommand, return " + stickyFlag);
+            return stickyFlag;
+        }
+    }
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index ba0398c..35021cf 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -26,6 +26,8 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.app.stubs.LocalForegroundService.ACTION_START_FGS_RESULT;
+import static android.app.stubs.LocalForegroundServiceSticky.ACTION_RESTART_FGS_STICKY_RESULT;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
@@ -36,6 +38,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.Instrumentation;
+import android.app.Service;
 import android.app.cts.android.app.cts.tools.ServiceConnectionHandler;
 import android.app.cts.android.app.cts.tools.ServiceProcessController;
 import android.app.cts.android.app.cts.tools.SyncOrderedBroadcast;
@@ -44,6 +47,7 @@
 import android.app.cts.android.app.cts.tools.WatchUidRunner;
 import android.app.stubs.CommandReceiver;
 import android.app.stubs.LocalForegroundServiceLocation;
+import android.app.stubs.LocalForegroundServiceSticky;
 import android.app.stubs.ScreenOnActivity;
 import android.content.ComponentName;
 import android.content.Context;
@@ -69,12 +73,15 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.AmMonitor;
 import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.function.Consumer;
+
 @RunWith(AndroidJUnit4.class)
 @Presubmit
 public class ActivityManagerProcessStateTest {
@@ -2177,6 +2184,76 @@
         }
     }
 
+    /**
+     * Test FGS compatibility with STICKY flag.
+     * @throws Exception
+     */
+    @Test
+    public void testFgsSticky() throws Exception {
+        // For START_STICKY, service is restarted, Service.onStartCommand is called with a null
+        // intent.
+        testFgsStickyInternal(Service.START_STICKY, ACTION_RESTART_FGS_STICKY_RESULT,
+                waiter -> waiter.doWait(WAITFOR_MSEC));
+        // For START_REDELIVER_INTENT, service is restarted, Service.onStartCommand is called with
+        // the same intent as previous service start.
+        testFgsStickyInternal(Service.START_REDELIVER_INTENT, ACTION_START_FGS_RESULT,
+                waiter -> waiter.doWait(WAITFOR_MSEC));
+        // For START_NOT_STICKY, service does not restart and Service.onStartCommand is not called
+        // again.
+        testFgsStickyInternal(Service.START_NOT_STICKY, ACTION_RESTART_FGS_STICKY_RESULT,
+                waiter -> {
+                    try {
+                        waiter.doWait(WAITFOR_MSEC);
+                        fail("Not-Sticky service should not restart after kill");
+                    } catch (Exception e) {
+                    }
+                });
+        testFgsStickyInternal(Service.START_NOT_STICKY, ACTION_START_FGS_RESULT,
+                waiter -> {
+                    try {
+                        waiter.doWait(WAITFOR_MSEC);
+                        fail("Not-Sticky service should not restart after kill");
+                    } catch (Exception e) {
+                    }
+                });
+    }
+
+    private void testFgsStickyInternal(int stickyFlag, String waitForBroadcastAction,
+            Consumer<WaitForBroadcast> checkKillResult) throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        AmMonitor monitor = new AmMonitor(mInstrumentation,
+                new String[]{AmMonitor.WAIT_FOR_EARLY_ANR, AmMonitor.WAIT_FOR_ANR});
+        try {
+            WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+            Bundle extras = new Bundle();
+            extras.putInt(LocalForegroundServiceSticky.STICKY_FLAG, stickyFlag);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_STICKY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, extras);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(waitForBroadcastAction);
+
+            CtsAppTestUtils.executeShellCmd(mInstrumentation,
+                    "am crash " + PACKAGE_NAME_APP1);
+            monitor.waitFor(AmMonitor.WAIT_FOR_CRASHED, WAITFOR_MSEC);
+            monitor.sendCommand(AmMonitor.CMD_KILL);
+            checkKillResult.accept(waiter);
+        } finally {
+            uid1Watcher.finish();
+            final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                am.forceStopPackage(PACKAGE_NAME_APP1);
+            });
+        }
+    }
+
     // Copied from android.test.InstrumentationTestCase
     /**
      * Utility method for launching an activity.