1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.server.wm.activity;
18 
19 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
20 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.junit.Assume.assumeTrue;
25 
26 import android.app.ActivityManager;
27 import android.app.ActivityOptions;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.os.Bundle;
32 import android.os.RemoteCallback;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.platform.test.annotations.Presubmit;
36 import android.server.wm.WindowManagerState;
37 import android.server.wm.WindowManagerStateHelper;
38 import android.util.Log;
39 
40 import androidx.test.platform.app.InstrumentationRegistry;
41 
42 import com.android.compatibility.common.util.FeatureUtil;
43 
44 import org.junit.AfterClass;
45 import org.junit.Before;
46 import org.junit.BeforeClass;
47 import org.junit.Test;
48 
49 import java.util.concurrent.CountDownLatch;
50 import java.util.concurrent.TimeUnit;
51 
52 @Presubmit
53 public class StartActivityAsUserTests {
54     static final String EXTRA_CALLBACK = "callback";
55     static final String KEY_USER_ID = "user id";
56 
57     private static final String PACKAGE = "android.server.wm.cts";
58     private static final String CLASS = "android.server.wm.activity.StartActivityAsUserActivity";
59     private static final int INVALID_STACK = -1;
60     private static final boolean SUPPORTS_MULTIPLE_USERS = UserManager.supportsMultipleUsers();
61 
62     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
63     private final ActivityManager mAm = mContext.getSystemService(ActivityManager.class);
64 
65     private static int sSecondUserId;
66 
67     private WindowManagerStateHelper mAmWmState = new WindowManagerStateHelper();
68     private static final String TAG = StartActivityAsUserTests.class.getSimpleName();
69 
70     @BeforeClass
createSecondUser()71     public static void createSecondUser() {
72         if (!SUPPORTS_MULTIPLE_USERS) {
73             return;
74         }
75 
76         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
77         // Create a CLONE profile to start a new activity as. On Automotive devices, CLONE profiles
78         // are not supported, thus create a FULL secondary user instead.
79         String createUserOptions = FeatureUtil.isAutomotive()
80                 ? "--user-type android.os.usertype.full.SECONDARY"
81                 : "--user-type android.os.usertype.profile.CLONE --profileOf "
82                         + context.getUserId();
83 
84         final String output = runShellCommand("pm create-user " + createUserOptions + " user2");
85 
86         try {
87             sSecondUserId = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
88         } catch (StringIndexOutOfBoundsException |  NumberFormatException e) {
89             Log.e(TAG, "Failed to create user of type android.os.usertype.profile.CLONE");
90         }
91 
92         if (sSecondUserId == 0) {
93             // A profile user was not successfully created.
94             // This will fail the tests in checkMultipleUsersNotSupportedOrSecondUserCreated().
95             return;
96         }
97         runShellCommand("pm install-existing --user " + sSecondUserId + " android.server.wm.cts");
98         runShellCommand("am start-user -w " + sSecondUserId);
99     }
100 
101     @AfterClass
removeSecondUser()102     public static void removeSecondUser() {
103         if (sSecondUserId == 0) {
104             return;
105         }
106         runShellCommand("am stop-user -w -f " + sSecondUserId);
107         runShellCommand("pm remove-user " + sSecondUserId);
108         sSecondUserId = 0;
109     }
110 
111     @Before
checkMultipleUsersNotSupportedOrSecondUserCreated()112     public void checkMultipleUsersNotSupportedOrSecondUserCreated() {
113         assumeTrue(SUPPORTS_MULTIPLE_USERS);
114         assertThat(sSecondUserId).isNotEqualTo(0);
115     }
116 
117     @Test
startActivityValidUser()118     public void startActivityValidUser() throws Throwable {
119         verifyStartActivityAsValidUser(false /* withOptions */);
120     }
121 
122     @Test
startActivityInvalidUser()123     public void startActivityInvalidUser() {
124         verifyStartActivityAsInvalidUser(false /* withOptions */);
125     }
126 
127     @Test
startActivityAsValidUserWithOptions()128     public void startActivityAsValidUserWithOptions() throws Throwable {
129         verifyStartActivityAsValidUser(true /* withOptions */);
130     }
131 
132     @Test
startActivityAsInvalidUserWithOptions()133     public void startActivityAsInvalidUserWithOptions() {
134         verifyStartActivityAsInvalidUser(true /* withOptions */);
135     }
136 
verifyStartActivityAsValidUser(boolean withOptions)137     private void verifyStartActivityAsValidUser(boolean withOptions) throws Throwable {
138         int[] secondUser = {-1};
139         CountDownLatch latch = new CountDownLatch(1);
140         RemoteCallback cb = new RemoteCallback((Bundle result) -> {
141             secondUser[0] = result.getInt(KEY_USER_ID);
142             latch.countDown();
143         });
144 
145         final Intent intent = new Intent(mContext, StartActivityAsUserActivity.class);
146         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
147         intent.putExtra(EXTRA_CALLBACK, cb);
148         UserHandle secondUserHandle = UserHandle.of(sSecondUserId);
149 
150         runWithShellPermissionIdentity(() -> {
151             if (withOptions) {
152                 mContext.startActivityAsUser(intent, ActivityOptions.makeBasic().toBundle(),
153                         secondUserHandle);
154             } else {
155                 mContext.startActivityAsUser(intent, secondUserHandle);
156             }
157         });
158 
159         latch.await(5, TimeUnit.SECONDS);
160         assertThat(secondUser[0]).isEqualTo(sSecondUserId);
161 
162         // The StartActivityAsUserActivity calls finish() in onCreate and here waits for the
163         // activity removed to prevent impacting other tests.
164         mAmWmState.waitForActivityRemoved(intent.getComponent());
165     }
166 
verifyStartActivityAsInvalidUser(boolean withOptions)167     private void verifyStartActivityAsInvalidUser(boolean withOptions) {
168         UserHandle secondUserHandle = UserHandle.of(sSecondUserId * 100);
169         int[] stackId = {-1};
170 
171         final Intent intent = new Intent(mContext, StartActivityAsUserActivity.class);
172         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
173 
174         runWithShellPermissionIdentity(() -> {
175             if (withOptions) {
176                 mContext.startActivityAsUser(intent, ActivityOptions.makeBasic().toBundle(),
177                         secondUserHandle);
178             } else {
179                 mContext.startActivityAsUser(intent, secondUserHandle);
180             }
181             WindowManagerState amState = mAmWmState;
182             amState.computeState();
183             ComponentName componentName = ComponentName.createRelative(PACKAGE, CLASS);
184             stackId[0] = amState.getRootTaskIdByActivity(componentName);
185         });
186 
187         assertThat(stackId[0]).isEqualTo(INVALID_STACK);
188     }
189 }
190