Add start info test for custom process name
Add test covering the start of an activity with a custom process name
and ensuring that a start info record is created and can be accessed.
Refactor fields into shared helper for use in both test activities.
Bug: 443344191
Test: atest ActivityManagerAppStartInfoTest with flag enabled and disabled
Flag: EXEMPT - new test only
Change-Id: I86e9eda9b30b85f06dc0c06c5d36b35220a23019
diff --git a/hostsidetests/devicepolicy/app/StartInfoApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/StartInfoApp/AndroidManifest.xml
index 04299ee..6590f14 100644
--- a/hostsidetests/devicepolicy/app/StartInfoApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/StartInfoApp/AndroidManifest.xml
@@ -31,6 +31,14 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
+ <activity android:name=".CustomProcessNameTestActivity"
+ android:process=":custom_process_name"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/ApiTestActivity.java b/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/ApiTestActivity.java
index 9b2d4c3..a663dc0 100644
--- a/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/ApiTestActivity.java
+++ b/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/ApiTestActivity.java
@@ -16,6 +16,23 @@
package com.android.cts.startinfoapp;
+import static com.android.cts.startinfoapp.TestHelper.REPLY_ACTION_COMPLETE;
+import static com.android.cts.startinfoapp.TestHelper.REPLY_EXTRA_FAILURE_VALUE;
+import static com.android.cts.startinfoapp.TestHelper.REPLY_EXTRA_SUCCESS_VALUE;
+import static com.android.cts.startinfoapp.TestHelper.REPLY_STATUS_NONE;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_KEY_ACTION;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_KEY_TIMESTAMP_KEY_FIRST;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_KEY_TIMESTAMP_KEY_LAST;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_KEY_TIMESTAMP_VALUE_FIRST;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_KEY_TIMESTAMP_VALUE_LAST;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_VALUE_ADD_TIMESTAMP;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_VALUE_CRASH;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_VALUE_LISTENER_ADD_MULTIPLE;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_VALUE_LISTENER_ADD_ONE;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_VALUE_LISTENER_ADD_REMOVE;
+import static com.android.cts.startinfoapp.TestHelper.REQUEST_VALUE_QUERY_START;
+import static com.android.cts.startinfoapp.TestHelper.reply;
+
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ApplicationStartInfo;
@@ -38,42 +55,6 @@
*/
public class ApiTestActivity extends Activity {
- private static final String REQUEST_KEY_ACTION = "action";
- private static final String REQUEST_KEY_TIMESTAMP_KEY_FIRST = "timestamp_key_first";
- private static final String REQUEST_KEY_TIMESTAMP_VALUE_FIRST = "timestamp_value_first";
- private static final String REQUEST_KEY_TIMESTAMP_KEY_LAST = "timestamp_key_last";
- private static final String REQUEST_KEY_TIMESTAMP_VALUE_LAST = "timestamp_value_last";
-
- // Request value for app to query and verify its own start.
- private static final int REQUEST_VALUE_QUERY_START = 1;
-
- // Request value for app to add the provided timestamp to start info.
- private static final int REQUEST_VALUE_ADD_TIMESTAMP = 2;
-
- // Request value for app to add a listener and respond when it gets triggered.
- private static final int REQUEST_VALUE_LISTENER_ADD_ONE = 3;
-
- // Request value for app to add 2 listeners and respond when each gets triggered.
- private static final int REQUEST_VALUE_LISTENER_ADD_MULTIPLE = 4;
-
- // Request value for app to add 2 listeners, remove 1, and respond success when correct one
- // is triggered and failure if incorrect one is triggered.
- private static final int REQUEST_VALUE_LISTENER_ADD_REMOVE = 5;
-
- // Request value for app to immediately crash. No reply will be sent.
- private static final int REQUEST_VALUE_CRASH = 6;
-
- // Broadcast action to return result for request.
- private static final String REPLY_ACTION_COMPLETE =
- "com.android.cts.startinfoapp.ACTION_COMPLETE";
-
- private static final String REPLY_EXTRA_STATUS_KEY = "status";
-
- private static final int REPLY_EXTRA_SUCCESS_VALUE = 1;
- private static final int REPLY_EXTRA_FAILURE_VALUE = 2;
-
- private static final int REPLY_STATUS_NONE = -1;
-
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
@@ -127,7 +108,9 @@
List<ApplicationStartInfo> starts = am.getHistoricalProcessStartReasons(1);
boolean success = starts != null && starts.size() == 1;
- reply(success ? REPLY_EXTRA_SUCCESS_VALUE : REPLY_EXTRA_FAILURE_VALUE);
+ reply(
+ ApiTestActivity.this,
+ success ? REPLY_EXTRA_SUCCESS_VALUE : REPLY_EXTRA_FAILURE_VALUE);
}
/**
@@ -146,7 +129,7 @@
am.addStartInfoTimestamp(keyFirst, valFirst);
am.addStartInfoTimestamp(keyLast, valLast);
- reply(REPLY_STATUS_NONE);
+ reply(ApiTestActivity.this, REPLY_STATUS_NONE);
}
/**
@@ -159,12 +142,13 @@
*/
private void addOneListener() {
ActivityManager am = getSystemService(ActivityManager.class);
- Consumer<ApplicationStartInfo> listener = new Consumer<ApplicationStartInfo>() {
- @Override
- public void accept(ApplicationStartInfo info) {
- reply(REPLY_EXTRA_SUCCESS_VALUE);
- }
- };
+ Consumer<ApplicationStartInfo> listener =
+ new Consumer<ApplicationStartInfo>() {
+ @Override
+ public void accept(ApplicationStartInfo info) {
+ reply(ApiTestActivity.this, REPLY_EXTRA_SUCCESS_VALUE);
+ }
+ };
am.addApplicationStartInfoCompletionListener(Executors.newSingleThreadScheduledExecutor(),
listener);
}
@@ -181,18 +165,20 @@
ActivityManager am = getSystemService(ActivityManager.class);
final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
- Consumer<ApplicationStartInfo> listenerFirst = new Consumer<ApplicationStartInfo>() {
- @Override
- public void accept(ApplicationStartInfo info) {
- reply(REPLY_EXTRA_SUCCESS_VALUE);
- }
- };
- Consumer<ApplicationStartInfo> listenerSecond = new Consumer<ApplicationStartInfo>() {
- @Override
- public void accept(ApplicationStartInfo info) {
- reply(REPLY_EXTRA_SUCCESS_VALUE);
- }
- };
+ Consumer<ApplicationStartInfo> listenerFirst =
+ new Consumer<ApplicationStartInfo>() {
+ @Override
+ public void accept(ApplicationStartInfo info) {
+ reply(ApiTestActivity.this, REPLY_EXTRA_SUCCESS_VALUE);
+ }
+ };
+ Consumer<ApplicationStartInfo> listenerSecond =
+ new Consumer<ApplicationStartInfo>() {
+ @Override
+ public void accept(ApplicationStartInfo info) {
+ reply(ApiTestActivity.this, REPLY_EXTRA_SUCCESS_VALUE);
+ }
+ };
am.addApplicationStartInfoCompletionListener(executor, listenerFirst);
am.addApplicationStartInfoCompletionListener(executor, listenerSecond);
@@ -214,22 +200,25 @@
final Object mLock = new Object();
final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
- Consumer<ApplicationStartInfo> listenerToRemove = new Consumer<ApplicationStartInfo>() {
- @Override
- public void accept(ApplicationStartInfo info) {
- synchronized (mLock) {
- reply(REPLY_EXTRA_FAILURE_VALUE);
- }
- }
- };
- Consumer<ApplicationStartInfo> listenerToTrigger = new Consumer<ApplicationStartInfo>() {
- @Override
- public void accept(ApplicationStartInfo info) {
- synchronized (mLock) {
- reply(REPLY_EXTRA_SUCCESS_VALUE);
- }
- };
- };
+ Consumer<ApplicationStartInfo> listenerToRemove =
+ new Consumer<ApplicationStartInfo>() {
+ @Override
+ public void accept(ApplicationStartInfo info) {
+ synchronized (mLock) {
+ reply(ApiTestActivity.this, REPLY_EXTRA_FAILURE_VALUE);
+ }
+ }
+ };
+ Consumer<ApplicationStartInfo> listenerToTrigger =
+ new Consumer<ApplicationStartInfo>() {
+ @Override
+ public void accept(ApplicationStartInfo info) {
+ synchronized (mLock) {
+ reply(ApiTestActivity.this, REPLY_EXTRA_SUCCESS_VALUE);
+ }
+ }
+ ;
+ };
am.addApplicationStartInfoCompletionListener(executor, listenerToRemove);
am.addApplicationStartInfoCompletionListener(executor, listenerToTrigger);
@@ -237,15 +226,6 @@
am.removeApplicationStartInfoCompletionListener(listenerToRemove);
}
- private void reply(int status) {
- Intent reply = new Intent();
- reply.setAction(REPLY_ACTION_COMPLETE);
- if (status != REPLY_STATUS_NONE) {
- reply.putExtra(REPLY_EXTRA_STATUS_KEY, status);
- }
- sendBroadcast(reply);
- }
-
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
diff --git a/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/CustomProcessNameTestActivity.java b/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/CustomProcessNameTestActivity.java
new file mode 100644
index 0000000..7d3cf3c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/CustomProcessNameTestActivity.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2025 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 com.android.cts.startinfoapp;
+
+import static com.android.cts.startinfoapp.TestHelper.REPLY_EXTRA_FAILURE_VALUE;
+import static com.android.cts.startinfoapp.TestHelper.REPLY_EXTRA_SUCCESS_VALUE;
+import static com.android.cts.startinfoapp.TestHelper.reply;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ApplicationStartInfo;
+import android.os.Bundle;
+
+import java.util.List;
+
+/**
+ * An activity with a custom process name to install to test ApplicationStartInfo.
+ *
+ * <p>A result will be provided back via a broadcast with action {@link REPLY_ACTION_COMPLETE} set
+ * for all cases, success and failure.
+ */
+public class CustomProcessNameTestActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ ActivityManager am = getSystemService(ActivityManager.class);
+ List<ApplicationStartInfo> starts = am.getHistoricalProcessStartReasons(1);
+
+ boolean success = starts != null && starts.size() == 1;
+ reply(this, success ? REPLY_EXTRA_SUCCESS_VALUE : REPLY_EXTRA_FAILURE_VALUE);
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/TestHelper.java b/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/TestHelper.java
new file mode 100644
index 0000000..0367904
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/TestHelper.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 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 com.android.cts.startinfoapp;
+
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Helper class to share constants and methods across test activities.
+ *
+ * <p>Constant values are kept in sync with {@link android.app.cts.ActivityManagerAppStartInfoTest}
+ * to ensure successful communication.
+ */
+public class TestHelper {
+ // LINT.IfChange
+ protected static final String REQUEST_KEY_ACTION = "action";
+ protected static final String REQUEST_KEY_TIMESTAMP_KEY_FIRST = "timestamp_key_first";
+ protected static final String REQUEST_KEY_TIMESTAMP_VALUE_FIRST = "timestamp_value_first";
+ protected static final String REQUEST_KEY_TIMESTAMP_KEY_LAST = "timestamp_key_last";
+ protected static final String REQUEST_KEY_TIMESTAMP_VALUE_LAST = "timestamp_value_last";
+
+ // Request value for app to query and verify its own start.
+ protected static final int REQUEST_VALUE_QUERY_START = 1;
+
+ // Request value for app to add the provided timestamp to start info.
+ protected static final int REQUEST_VALUE_ADD_TIMESTAMP = 2;
+
+ // Request value for app to add a listener and respond when it gets triggered.
+ protected static final int REQUEST_VALUE_LISTENER_ADD_ONE = 3;
+
+ // Request value for app to add 2 listeners and respond when each gets triggered.
+ protected static final int REQUEST_VALUE_LISTENER_ADD_MULTIPLE = 4;
+
+ // Request value for app to add 2 listeners, remove 1, and respond success when correct one
+ // is triggered and failure if incorrect one is triggered.
+ protected static final int REQUEST_VALUE_LISTENER_ADD_REMOVE = 5;
+
+ // Request value for app to immediately crash. No reply will be sent.
+ protected static final int REQUEST_VALUE_CRASH = 6;
+
+ // Broadcast action to return result for request.
+ protected static final String REPLY_ACTION_COMPLETE =
+ "com.android.cts.startinfoapp.ACTION_COMPLETE";
+
+ protected static final String REPLY_EXTRA_STATUS_KEY = "status";
+
+ protected static final int REPLY_EXTRA_SUCCESS_VALUE = 1;
+ protected static final int REPLY_EXTRA_FAILURE_VALUE = 2;
+
+ protected static final int REPLY_STATUS_NONE = -1;
+
+ // LINT.ThenChange(//tests/app/AppStartTest/src/android/app/cts/ActivityManagerAppStartInfoTest.java)
+
+ /** Send a broadcast with a test result status. */
+ protected static void reply(Context context, int status) {
+ Intent reply = new Intent();
+ reply.setAction(REPLY_ACTION_COMPLETE);
+ if (status != REPLY_STATUS_NONE) {
+ reply.putExtra(REPLY_EXTRA_STATUS_KEY, status);
+ }
+ context.sendBroadcast(reply);
+ }
+}
diff --git a/tests/app/AppStartTest/Android.bp b/tests/app/AppStartTest/Android.bp
index 0bb02b3..288b92a 100644
--- a/tests/app/AppStartTest/Android.bp
+++ b/tests/app/AppStartTest/Android.bp
@@ -33,6 +33,7 @@
"CtsExternalServiceCommon",
"cts-wm-util",
"libprotobuf-java-lite",
+ "am_flags_lib",
],
srcs: [
"src/**/*.java",
diff --git a/tests/app/AppStartTest/src/android/app/cts/ActivityManagerAppStartInfoTest.java b/tests/app/AppStartTest/src/android/app/cts/ActivityManagerAppStartInfoTest.java
index 705b1d9..4653a78 100644
--- a/tests/app/AppStartTest/src/android/app/cts/ActivityManagerAppStartInfoTest.java
+++ b/tests/app/AppStartTest/src/android/app/cts/ActivityManagerAppStartInfoTest.java
@@ -34,6 +34,9 @@
import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -47,6 +50,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,9 +60,12 @@
@RunWith(AndroidJUnit4.class)
public final class ActivityManagerAppStartInfoTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String TAG = ActivityManagerAppStartInfoTest.class.getSimpleName();
- // Begin section: keep in sync with {@link ApiTestActivity}
+ // LINT.IfChange
private static final String REQUEST_KEY_ACTION = "action";
private static final String REQUEST_KEY_TIMESTAMP_KEY_FIRST = "timestamp_key_first";
private static final String REQUEST_KEY_TIMESTAMP_VALUE_FIRST = "timestamp_value_first";
@@ -81,12 +88,12 @@
//private static final int REPLY_EXTRA_FAILURE_VALUE = 2;
private static final int REPLY_STATUS_NONE = -1;
- // End section: keep in sync with {@link ApiTestActivity}
+ // LINT.ThenChange(//hostsidetests/devicepolicy/app/StartInfoApp/src/com/android/cts/startinfoapp/TestHelper.java)
- private static final String STUB_APK =
- "/data/local/tmp/cts/content/CtsAppStartInfoApp.apk";
+ private static final String STUB_APK = "/data/local/tmp/cts/content/CtsAppStartInfoApp.apk";
private static final String STUB_PACKAGE_NAME = "com.android.cts.startinfoapp";
private static final String SIMPLE_ACTIVITY = ".ApiTestActivity";
+ private static final String CUSTOM_PROCESS_ACTIVITY = ".CustomProcessNameTestActivity";
private static final int FIRST_TIMESTAMP_KEY =
ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START;
@@ -282,6 +289,45 @@
}
/**
+ * Test querying the startup of a custom process name activity.
+ *
+ * <p>This is the only test needed for custom process name as the impact of a custom process
+ * name is only on activity based starts, which need to look up the process record. Within
+ * Activity, the impact is limited to creation of the record as once the record is created, it
+ * is indexed by package name and uid, not process name.
+ */
+ @Test
+ @RequiresFlagsEnabled(com.android.server.am.Flags.FLAG_APP_START_INFO_PROCESS_NAME_FIX)
+ public void testQueryThisProcessCustomProcessName() throws Exception {
+ clearHistoricalStartInfo();
+
+ ResultReceiverFilter receiver = new ResultReceiverFilter(REPLY_ACTION_COMPLETE, 1);
+
+ // Start the app and have it query its own start info record.
+ executeShellCmd(
+ "am start --user %d -n %s/%s%s --ei %s %d",
+ mTestRunningUserId, // test running user ID
+ STUB_PACKAGE_NAME,
+ STUB_PACKAGE_NAME,
+ CUSTOM_PROCESS_ACTIVITY,
+ REQUEST_KEY_ACTION,
+ REQUEST_VALUE_QUERY_START); // action to perform
+
+ // Wait for complete callback
+ assertEquals(RESULT_PASS, receiver.waitForActivity());
+ receiver.close();
+
+ // Confirm that the app confirmed that it successfully obtained record.
+ assertEquals(1, receiver.mIntents.size());
+
+ Bundle extras = receiver.mIntents.get(0).getExtras();
+ assertNotNull(extras);
+
+ int status = extras.getInt(REPLY_EXTRA_STATUS_KEY, -1);
+ assertEquals(REPLY_EXTRA_SUCCESS_VALUE, status);
+ }
+
+ /**
* Test adding timestamps and verify that the timestamps that were added are still there on a
* subsequent query.
*