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.