RESTRICT AUTOMERGE Add test of startActivities with different uids

Ensure the activity with different uid cannot be the caller
of other the activities. That also means they won't be in
the same tasks.

Bug: 145669109
Test: run cts --test android.server.cts.StartActivityTests \
      -m CtsServicesHostTestCases

Change-Id: Iae04eb622cb6f868699a6e8e857d63643100a532
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
index f9a86c6..c7e637a 100644
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/app/src/android/server/cts/TestActivity.java
@@ -17,6 +17,7 @@
 package android.server.cts;
 
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -31,14 +32,37 @@
     // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
     private static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
 
+    // The target activity names for ACTION_START_ACTIVITIES.
+    private static final String EXTRA_NAMES = "names";
+
+    // The target activity flags for ACTION_START_ACTIVITIES.
+    private static final String EXTRA_FLAGS = "flags";
+
     // Finishes the activity
     private static final String ACTION_FINISH_SELF = "android.server.cts.TestActivity.finish_self";
 
+    // Calls startActivities with the provided targets.
+    private static final String ACTION_START_ACTIVITIES =
+            "android.server.cts.TestActivity.start_activities";
+
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent != null && intent.getAction().equals(ACTION_FINISH_SELF)) {
+            final String action = intent.getAction();
+            if (ACTION_FINISH_SELF.equals(action)) {
                 finish();
+            } else if (ACTION_START_ACTIVITIES.equals(action)) {
+                final int[] flags = intent.getIntArrayExtra(EXTRA_FLAGS);
+                final String[] names = intent.getStringArrayExtra(EXTRA_NAMES);
+                final Intent[] intents = new Intent[names.length];
+                for (int i = 0; i < intents.length; i++) {
+                    Log.i(TAG, "Start activities[" + i + "]=" + names[i]
+                            + " fl=0x" + Integer.toHexString(flags[i]));
+                    intents[i] = new Intent()
+                            .setComponent(ComponentName.unflattenFromString(names[i]))
+                            .addFlags(flags[i]);
+                }
+                startActivities(intents);
             }
         }
     };
@@ -57,7 +81,10 @@
     @Override
     protected void onStart() {
         super.onStart();
-        registerReceiver(mReceiver, new IntentFilter(ACTION_FINISH_SELF));
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_FINISH_SELF);
+        intentFilter.addAction(ACTION_START_ACTIVITIES);
+        registerReceiver(mReceiver, intentFilter);
     }
 
     @Override
diff --git a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/StartActivityTests.java b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/StartActivityTests.java
new file mode 100644
index 0000000..5a0c470
--- /dev/null
+++ b/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/StartActivityTests.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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.server.cts;
+
+import java.util.Arrays;
+
+/**
+ * Build: mmma -j32 cts/hostsidetests/services
+ * Run: cts/hostsidetests/services/activityandwindowmanager/util/run-test \
+ *      CtsServicesHostTestCases android.server.cts.StartActivityTests
+ */
+public class StartActivityTests extends ActivityManagerTestBase {
+
+    private static final String TEST_ACTIVITY_ACTION_START_ACTIVITIES =
+            "android.server.cts.TestActivity.start_activities";
+    private static final String TEST_ACTIVITY = "android.server.cts/.TestActivity";
+    private static final String NO_RELAUNCH_ACTIVITY = "android.server.cts/.NoRelaunchActivity";
+    private static final String LAUNCHING_ACTIVITY = "android.server.cts/.LaunchingActivity";
+    private static final String SECOND_ACTIVITY = "android.server.cts.second/.SecondActivity";
+    private static final String FLAG_ACTIVITY_NEW_TASK = "0x10000000";
+
+    /**
+     * Assume there are 3 activities (A1, A2, A3) with different task affinities and the same uid.
+     * After A1 called {@link Activity#startActivities} to start A2 (with NEW_TASK) and A3, the
+     * result should be 2 tasks: [A1] and [A2, A3].
+     */
+    public void testStartActivitiesInNewAndSameTask() throws Exception {
+        final int[] taskIds = startActivitiesAndGetTaskIds(
+                new String[] { NO_RELAUNCH_ACTIVITY, LAUNCHING_ACTIVITY },
+                new String[] { FLAG_ACTIVITY_NEW_TASK, "0" });
+
+        assertNotSame("The activity with different task affinity started by flag NEW_TASK"
+                + " should be in a different task", taskIds[0], taskIds[1]);
+        assertEquals("The activity started without flag NEW_TASK should be put in the same task",
+                taskIds[1], taskIds[2]);
+    }
+
+    /**
+     * Assume there are 3 activities (A1, A2, B1) with default launch mode. The uid of B1 is
+     * different from A1 and A2. After A1 called {@link Activity#startActivities} to start B1 and
+     * A2, the result should be 3 tasks.
+     */
+    public void testStartActivitiesWithDiffUidNotInSameTask() throws Exception {
+        final int[] taskIds = startActivitiesAndGetTaskIds(
+                new String[] { SECOND_ACTIVITY, LAUNCHING_ACTIVITY },
+                new String[] { FLAG_ACTIVITY_NEW_TASK, "0" });
+
+        assertNotSame("The activity in a different application (uid) started by flag NEW_TASK"
+                + " should be in a different task", taskIds[0], taskIds[1]);
+        assertFalse("The last started activity should be in a different task because "
+                + SECOND_ACTIVITY + " has a different uid from the source caller",
+                Arrays.asList(taskIds[0], taskIds[1]).contains(taskIds[2]));
+    }
+
+    /**
+     * Invokes {@link android.app.Activity#startActivities} from {@link #TEST_ACTIVITY} and returns
+     * the task id of each started activity (the index 0 will be the caller {@link #TEST_ACTIVITY}).
+     */
+    private int[] startActivitiesAndGetTaskIds(String[] activityNames, String[] activityFlags)
+            throws Exception {
+        launchActivity(TEST_ACTIVITY);
+        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_START_ACTIVITIES
+                + " --esa names " + String.join(",", activityNames)
+                + " --eia flags " + String.join(",", activityFlags));
+
+        final int[] taskIds = new int[activityNames.length + 1];
+        // The activities are started, wait for the last (top) activity to be ready and then verify
+        // their task ids.
+        mAmWmState.computeState(mDevice, new String[] { activityNames[activityNames.length - 1] });
+        final ActivityManagerState amState = mAmWmState.getAmState();
+        taskIds[0] = amState.getTaskByActivityName(TEST_ACTIVITY).mTaskId;
+        for (int i = 0; i < activityNames.length; i++) {
+            taskIds[i + 1] = amState.getTaskByActivityName(activityNames[i]).mTaskId;
+        }
+        return taskIds;
+    }
+}
diff --git a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
index 683bdff..f9a473d 100644
--- a/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activityandwindowmanager/util/src/android/server/cts/ActivityManagerTestBase.java
@@ -192,7 +192,15 @@
         return name != null && name.contains(".");
     }
 
+    private static boolean isShortComponentName(String name) {
+        return name != null && name.contains("/.");
+    }
+
+    /** The dump output of ActivityManager uses short component name. */
     static String getActivityComponentName(final String packageName, final String activityName) {
+        if (isShortComponentName(activityName)) {
+            return activityName;
+        }
         return packageName + "/" + (isFullyQualifiedActivityName(activityName) ? "" : ".") +
                 activityName;
     }
@@ -219,7 +227,14 @@
         return getWindowName(componentName, activityName);
     }
 
+    /** The dump output of WindowManager uses fully qualified component name. */
     static String getWindowName(final String packageName, final String activityName) {
+        if (isShortComponentName(activityName)) {
+            final int sep = activityName.indexOf('/');
+            final String pkg = activityName.substring(0, sep);
+            final String cls = activityName.substring(sep + 1);
+            return pkg + "/" + pkg + cls;
+        }
         return getBaseWindowName(packageName, !isFullyQualifiedActivityName(activityName))
                 + activityName;
     }