1 /*
2  * Copyright (C) 2024 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.car.app.cts;
18 
19 import static android.Manifest.permission.CREATE_USERS;
20 import static android.car.CarOccupantZoneManager.INVALID_USER_ID;
21 import static android.car.cts.utils.ShellPermissionUtils.runWithShellPermissionIdentity;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 import static com.google.common.truth.Truth.assertWithMessage;
25 
26 import static org.junit.Assume.assumeTrue;
27 
28 import android.app.ActivityManager;
29 import android.car.Car;
30 import android.car.CarOccupantZoneManager;
31 import android.car.SyncResultCallback;
32 import android.car.test.PermissionsCheckerRule;
33 import android.car.test.PermissionsCheckerRule.EnsureHasPermission;
34 import android.car.user.CarUserManager;
35 import android.car.user.UserCreationRequest;
36 import android.car.user.UserCreationResult;
37 import android.content.ComponentName;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.content.pm.ResolveInfo;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.view.Display;
44 
45 import androidx.test.filters.SmallTest;
46 import androidx.test.platform.app.InstrumentationRegistry;
47 import androidx.test.uiautomator.By;
48 import androidx.test.uiautomator.BySelector;
49 import androidx.test.uiautomator.UiDevice;
50 import androidx.test.uiautomator.UiObject2;
51 import androidx.test.uiautomator.Until;
52 
53 import com.android.compatibility.common.util.ShellUtils;
54 
55 import org.junit.After;
56 import org.junit.Before;
57 import org.junit.Rule;
58 import org.junit.Test;
59 
60 import java.util.ArrayList;
61 import java.util.List;
62 import java.util.concurrent.TimeUnit;
63 
64 @SmallTest
65 public class MultiUserMultiDisplayWindowSecurityTest {
66     private static final long TIMEOUT_MS = 5_000L;
67     private static final long USER_CREATION_TIMEOUT_MS = 20_000L;
68 
69     private static final String EXTRA_DISPLAY_ID_TO_SHOW_OVERLAY =
70             "android.car.app.cts.DISPLAY_ID_TO_SHOW_OVERLAY";
71     private static final ComponentName COMPONENT_SDK22_OVERLAY_WINDOW_TEST_ACTIVITY =
72             ComponentName.createRelative("android.car.app.cts.overlaywindowappsdk22",
73                     ".OverlayWindowTestActivitySdk22");
74     private static final ComponentName COMPONENT_DEPRECATED_SDK_TEST_ACTIVITY =
75             ComponentName.createRelative("android.car.app.cts.deprecatedsdk", ".MainActivity");
76 
77     private static final String PREFIX_NEW_USER_NAME = "newTestUser";
78 
79     @Rule
80     public final PermissionsCheckerRule mPermissionsCheckerRule = new PermissionsCheckerRule();
81 
82     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
83     private final ActivityManager mActivityManager = mContext.getSystemService(
84             ActivityManager.class);
85     private final UserManager mUserManager = mContext.getSystemService(UserManager.class);
86     private final List<Integer> mUsersToRemove = new ArrayList<>();
87     private CarUserManager mCarUserManager;
88     private CarOccupantZoneManager mCarOccupantZoneManager;
89     private UiDevice mDevice;
90     private List<Integer> mUnassignedDisplays;
91 
92     @Before
setUp()93     public void setUp() throws Exception {
94         assumeTrue("Skipping test: Device doesn't support visible background users",
95                 mUserManager.isVisibleBackgroundUsersSupported());
96 
97         Car car = Car.createCar(mContext);
98         mCarUserManager = car.getCarManager(CarUserManager.class);
99         mCarOccupantZoneManager = car.getCarManager(CarOccupantZoneManager.class);
100         mUnassignedDisplays = getUnassignedDisplaysForUser(UserHandle.myUserId());
101         assumeTrue("There should be at least one unassigned display.",
102                 mUnassignedDisplays.size() > 0);
103 
104         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
105         mDevice.wakeUp();
106     }
107 
108     @After
tearDown()109     public void tearDown() throws Exception {
110         stopTestPackage(COMPONENT_DEPRECATED_SDK_TEST_ACTIVITY);
111         stopTestPackage(COMPONENT_SDK22_OVERLAY_WINDOW_TEST_ACTIVITY);
112         cleanUpTestUsers();
113     }
114 
115     @Test
testDeprecatedSdkVersionDialog()116     public void testDeprecatedSdkVersionDialog() {
117         mContext.startActivity(createIntent(COMPONENT_DEPRECATED_SDK_TEST_ACTIVITY));
118 
119         String windowName = COMPONENT_DEPRECATED_SDK_TEST_ACTIVITY.getPackageName();
120         BySelector appWarningDialogSelector = By.pkg("android").text(windowName);
121         assumeTrue(mDevice.wait(Until.hasObject(appWarningDialogSelector), TIMEOUT_MS));
122 
123         UiObject2 appWarningUiObject = mDevice.findObject(appWarningDialogSelector);
124         for (int unassignedDisplayId : mUnassignedDisplays) {
125             assertWithMessage("AppWarning dialog must not be displayed on the unassigned display.")
126                     .that(appWarningUiObject.getDisplayId() == unassignedDisplayId)
127                     .isFalse();
128         }
129 
130         // Go back to dismiss the app warning dialog.
131         mDevice.pressBack();
132     }
133 
134     @Test
135     @EnsureHasPermission({CREATE_USERS})
testShowOverlayWindowOnDisplayAssignedToDifferentUser()136     public void testShowOverlayWindowOnDisplayAssignedToDifferentUser() throws Exception {
137         // UserPicker has the flag to hide non-system overlay windows when it is visible.
138         // (@see WindowManager.LayoutParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
139         // To ensure the test is performed normally, assign each display to a user,
140         // leading to the dismissal of UserPicker.
141         ensureAllDisplaysAreAssignedToUser();
142         // Just pick the first one.
143         int displayAssignedToDifferentUser = mUnassignedDisplays.get(0);
144         Intent intent = createIntent(COMPONENT_SDK22_OVERLAY_WINDOW_TEST_ACTIVITY);
145         intent.putExtra(EXTRA_DISPLAY_ID_TO_SHOW_OVERLAY, displayAssignedToDifferentUser);
146 
147         mContext.startActivity(intent);
148         String packageName = COMPONENT_SDK22_OVERLAY_WINDOW_TEST_ACTIVITY.getPackageName();
149         // Wait for the app to appear
150         mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), TIMEOUT_MS);
151 
152         BySelector overlayWindowSelector = By.text(packageName)
153                 .displayId(displayAssignedToDifferentUser).depth(0);
154         assertWithMessage("Should not display the overlay window on a display assigned to a user"
155                 + " other than user %s", UserHandle.myUserId())
156                 .that(mDevice.hasObject(overlayWindowSelector))
157                 .isFalse();
158     }
159 
getUnassignedDisplaysForUser(int userId)160     private List<Integer> getUnassignedDisplaysForUser(int userId) {
161         List<CarOccupantZoneManager.OccupantZoneInfo> zonelist =
162                 mCarOccupantZoneManager.getAllOccupantZones();
163         List<Integer> displayList = new ArrayList<>();
164         for (CarOccupantZoneManager.OccupantZoneInfo zone : zonelist) {
165             int userForOccupantZone = mCarOccupantZoneManager.getUserForOccupant(zone);
166             if (userForOccupantZone == userId) {
167                 // Skip the assigned zone.
168                 continue;
169             }
170             Display display = mCarOccupantZoneManager.getDisplayForOccupant(zone,
171                     CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
172             if (display != null) {
173                 displayList.add(display.getDisplayId());
174             }
175         }
176         return displayList;
177     }
178 
ensureAllDisplaysAreAssignedToUser()179     private void ensureAllDisplaysAreAssignedToUser() throws Exception {
180         String passengerLauncherPkg = getPassengerLauncherPackageName();
181         for (int displayId : mUnassignedDisplays) {
182             int userId = mCarOccupantZoneManager.getUserForDisplayId(displayId);
183             if (userId != INVALID_USER_ID) {
184                 // A user is already assigned to the display
185                 continue;
186             }
187             userId = createNewUser(displayId);
188             mUsersToRemove.add(userId);
189             // Skip the setup wizard and make sure the passenger home shows up.
190             setUserSetupComplete(userId);
191             startUserOnDisplay(userId, displayId);
192             waitForPassengerLauncherOnDisplay(passengerLauncherPkg, displayId);
193         }
194     }
195 
createNewUser(int displayId)196     private int createNewUser(int displayId) throws Exception {
197         String userName = new StringBuilder(PREFIX_NEW_USER_NAME).append(displayId).toString();
198         SyncResultCallback<UserCreationResult> userCreationResultCallback =
199                 new SyncResultCallback<>();
200 
201         mCarUserManager.createUser(new UserCreationRequest.Builder().setName(userName).build(),
202                 Runnable::run, userCreationResultCallback);
203         UserCreationResult result = userCreationResultCallback.get(USER_CREATION_TIMEOUT_MS,
204                 TimeUnit.MILLISECONDS);
205 
206         assertThat(result).isNotNull();
207         assertThat(result.isSuccess()).isTrue();
208         UserHandle user = result.getUser();
209         assertThat(user).isNotNull();
210         return user.getIdentifier();
211     }
212 
createIntent(ComponentName activity)213     private static Intent createIntent(ComponentName activity) {
214         Intent intent = new Intent();
215         intent.setComponent(activity);
216         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
217         return intent;
218     }
219 
setUserSetupComplete(int userId)220     private static void setUserSetupComplete(int userId) {
221         ShellUtils.runShellCommand("settings put --user %d secure user_setup_complete 1", userId);
222         assertWithMessage("User setup complete").that(
223                 ShellUtils.runShellCommand("settings get --user %d secure user_setup_complete",
224                         userId)).isEqualTo("1");
225     }
226 
deleteUserSetupComplete(int userId)227     private static void deleteUserSetupComplete(int userId) {
228         assertWithMessage("User setup complete setting deleted").that(
229                 ShellUtils.runShellCommand("settings delete --user %d secure user_setup_complete",
230                         userId)).contains("Deleted 1 rows");
231     }
232 
startUserOnDisplay(int userId, int displayId)233     private static void startUserOnDisplay(int userId, int displayId) {
234         assertWithMessage("User started").that(
235                 ShellUtils.runShellCommand("am start-user -w --display %d %d", displayId, userId))
236                 .contains("Success: user started on display " + displayId);
237     }
238 
waitForPassengerLauncherOnDisplay(String passengerLauncherPkg, int displayId)239     private void waitForPassengerLauncherOnDisplay(String passengerLauncherPkg, int displayId) {
240         mDevice.wait(Until.hasObject(By.pkg(passengerLauncherPkg).displayId(displayId).depth(0)),
241                 TIMEOUT_MS);
242     }
243 
cleanUpTestUsers()244     private void cleanUpTestUsers() {
245         if (mUsersToRemove.isEmpty()) {
246             return;
247         }
248         for (int userId : mUsersToRemove) {
249             deleteUserSetupComplete(userId);
250             stopUser(userId);
251             removeUser(userId);
252         }
253         mUsersToRemove.clear();
254     }
255 
stopUser(int userId)256     private static void stopUser(int userId) {
257         ShellUtils.runShellCommand("am stop-user -w -f %d", userId);
258     }
259 
removeUser(int userId)260     private static void removeUser(int userId) {
261         assertWithMessage("User removed").that(
262                 ShellUtils.runShellCommand("pm remove-user --wait %d",
263                         userId)).contains("Success: removed user");
264     }
265 
stopTestPackage(ComponentName activityName)266     private void stopTestPackage(ComponentName activityName) {
267         runWithShellPermissionIdentity(() -> mActivityManager.forceStopPackage(
268                 activityName.getPackageName()));
269     }
270 
getPassengerLauncherPackageName()271     private String getPassengerLauncherPackageName() {
272         Intent queryIntent = new Intent(Intent.ACTION_MAIN);
273         queryIntent.addCategory(Intent.CATEGORY_SECONDARY_HOME);
274         List<ResolveInfo> resolveInfo = mContext.getPackageManager()
275                 .queryIntentActivities(queryIntent, /* flags= */ 0);
276         assertThat(resolveInfo).isNotNull();
277         assertThat(resolveInfo).isNotEmpty();
278         return resolveInfo.get(0).activityInfo.packageName;
279     }
280 }
281