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.
      *