Test SDK sandbox creation per user

Check that a unique sandbox is created for each user calling loadSdk()
from the same app.

Bug: 221024799
Test: atest SdkSandboxLifecycleHostTest#testSandboxIsCreatedPerUser
Change-Id: I373fca36eb9656b08e5093bc1b2a4f8619ce9492
diff --git a/sdksandbox/tests/hostsidetests/Android.bp b/sdksandbox/tests/hostsidetests/Android.bp
index 0d7e9bc..7f5a443 100644
--- a/sdksandbox/tests/hostsidetests/Android.bp
+++ b/sdksandbox/tests/hostsidetests/Android.bp
@@ -28,6 +28,7 @@
     static_libs: [
         "cts-install-lib-host",
         "modules-utils-build-testing",
+        "SdkSandboxHostTestUtils",
     ],
     data: [
         ":TestCodeProvider",
diff --git a/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java b/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java
index 1eecd5d..03404ba 100644
--- a/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java
+++ b/sdksandbox/tests/hostsidetests/src/com/android/tests/sdksandbox/host/SdkSandboxLifecycleHostTest.java
@@ -20,6 +20,8 @@
 
 import static org.junit.Assume.assumeTrue;
 
+import android.app.sdksandbox.hosttestutils.SecondaryUserUtils;
+
 import com.android.modules.utils.build.testing.DeviceSdkLevel;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -34,6 +36,7 @@
 public final class SdkSandboxLifecycleHostTest extends BaseHostJUnit4Test {
 
     private static final String APP_PACKAGE = "com.android.sdksandbox.app";
+    private static final String APP_APK = "SdkSandboxTestApp.apk";
     private static final String APP_2_PACKAGE = "com.android.sdksandbox.app2";
 
     private static final String APP_SHARED_PACKAGE = "com.android.sdksandbox.shared.app1";
@@ -60,18 +63,11 @@
     private static final String SANDBOX_SHARED_2_PROCESS_NAME =
             APP_SHARED_2_PACKAGE + "_sdk_sandbox";
 
+    private final SecondaryUserUtils mUserUtils = new SecondaryUserUtils(this);
+
     private boolean mWasRoot;
     private DeviceSdkLevel mDeviceSdkLevel;
 
-    private void startActivity(String pkg, String activity) throws Exception {
-        getDevice().executeShellCommand(String.format("am start -W -n %s/.%s", pkg, activity));
-    }
-
-    private void killApp(String pkg) throws Exception {
-        getDevice().executeShellCommand(String.format("am force-stop %s", pkg));
-        waitForProcessDeath(pkg + '\n');
-    }
-
     @Before
     public void setUp() throws Exception {
         assertThat(getBuild()).isNotNull();
@@ -85,6 +81,7 @@
 
     @After
     public void tearDown() throws Exception {
+        mUserUtils.removeSecondaryUserIfNecessary();
         cleanUpAppAndSandboxProcesses();
 
         if (!mWasRoot) {
@@ -135,6 +132,40 @@
     }
 
     @Test
+    public void testSandboxIsCreatedPerUser() throws Exception {
+        assumeTrue(getDevice().isMultiUserSupported());
+
+        int initialUserId = getDevice().getCurrentUser();
+        int secondaryUserId = mUserUtils.createAndStartSecondaryUser();
+        installPackageAsUser(APP_APK, false, secondaryUserId);
+
+        // Start app for the primary user
+        startActivityAsUser(APP_PACKAGE, APP_ACTIVITY, initialUserId);
+        String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
+        assertThat(processDump).contains(APP_PACKAGE + '\n');
+        assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
+
+        getDevice().switchUser(secondaryUserId);
+
+        // Should still see an app/sdk sandbox running.
+        processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
+        assertThat(processDump).contains(APP_PACKAGE + '\n');
+        assertThat(processDump).contains(SANDBOX_1_PROCESS_NAME);
+
+        // Start the app for the secondary user.
+        startActivityAsUser(APP_PACKAGE, APP_ACTIVITY, secondaryUserId);
+        // There should be two instances of app and sandbox processes - one for each user.
+        assertThat(getProcessOccurrenceCount(APP_PACKAGE + '\n')).isEqualTo(2);
+        assertThat(getProcessOccurrenceCount(SANDBOX_1_PROCESS_NAME)).isEqualTo(2);
+
+        // Kill the app process for the secondary user.
+        killApp(APP_PACKAGE + '\n');
+        // There should be one instance of app and sandbox process after kill
+        assertThat(getProcessOccurrenceCount(APP_PACKAGE + '\n')).isEqualTo(1);
+        assertThat(getProcessOccurrenceCount(SANDBOX_1_PROCESS_NAME)).isEqualTo(1);
+    }
+
+    @Test
     public void testAppAndSdkSandboxAreKilledOnLoadedSdkUpdate() throws Exception {
         startActivity(APP_PACKAGE, APP_ACTIVITY);
 
@@ -331,6 +362,34 @@
         assertThat(finalSandboxOomScoreAdj).isGreaterThan(initialSandboxOomScoreAdj);
     }
 
+    private void startActivity(String pkg, String activity) throws Exception {
+        getDevice().executeShellCommand(String.format("am start -W -n %s/.%s", pkg, activity));
+    }
+
+    private void startActivityAsUser(String pkg, String activity, int userId) throws Exception {
+        getDevice()
+                .executeShellCommand(
+                        String.format("am start -W -n %s/.%s --user %d", pkg, activity, userId));
+    }
+
+    private void killApp(String pkg) throws Exception {
+        getDevice().executeShellCommand(String.format("am force-stop --user current %s", pkg));
+        waitForProcessDeath(pkg + '\n');
+    }
+
+    // Get the number of running processes with the given process name.
+    private int getProcessOccurrenceCount(String processName) throws Exception {
+        String processDump = getDevice().executeAdbCommand("shell", "ps", "-A");
+
+        int count = 0;
+        int processOccurrenceIndex = processDump.indexOf(processName);
+        while (processOccurrenceIndex >= 0) {
+            count++;
+            processOccurrenceIndex = processDump.indexOf(processName, processOccurrenceIndex + 1);
+        }
+        return count;
+    }
+
     private void cleanUpAppAndSandboxProcesses() throws Exception {
         for (String pkg :
                 new String[] {