1 /* 2 * Copyright (C) 2022 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 com.android.server.wm; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.annotation.UserIdInt; 23 import android.car.app.CarActivityManager; 24 import android.car.builtin.os.UserManagerHelper; 25 import android.car.builtin.util.Slogf; 26 import android.car.builtin.view.DisplayHelper; 27 import android.car.builtin.window.DisplayAreaOrganizerHelper; 28 import android.content.ComponentName; 29 import android.hardware.display.DisplayManager; 30 import android.os.ServiceSpecificException; 31 import android.util.ArrayMap; 32 import android.util.Log; 33 import android.util.Pair; 34 import android.util.SparseIntArray; 35 import android.view.Display; 36 37 import com.android.car.internal.util.IndentingPrintWriter; 38 import com.android.internal.annotations.GuardedBy; 39 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.List; 43 44 /** 45 * Implementation of {@link CarLaunchParamsModifierUpdatable}. 46 * 47 * @hide 48 */ 49 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 50 public final class CarLaunchParamsModifierUpdatableImpl 51 implements CarLaunchParamsModifierUpdatable { 52 private static final String TAG = CarLaunchParamsModifierUpdatableImpl.class.getSimpleName(); 53 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 54 // Comes from android.os.UserHandle.USER_NULL. 55 private static final int USER_NULL = -10000; 56 57 private final CarLaunchParamsModifierInterface mBuiltin; 58 private final Object mLock = new Object(); 59 60 // Always start with USER_SYSTEM as the timing of handleCurrentUserSwitching(USER_SYSTEM) is not 61 // guaranteed to be earler than 1st Activity launch. 62 @GuardedBy("mLock") 63 private int mDriverUser = UserManagerHelper.USER_SYSTEM; 64 65 // TODO: Switch from tracking displays to tracking display areas instead 66 /** 67 * This one is for holding all passenger (=profile user) displays which are mostly static unless 68 * displays are added / removed. Note that {@link #mDisplayToProfileUserMapping} can be empty 69 * while user is assigned and that cannot always tell if specific display is for driver or not. 70 */ 71 @GuardedBy("mLock") 72 private final ArrayList<Integer> mPassengerDisplays = new ArrayList<>(); 73 74 /** key: display id, value: profile user id */ 75 @GuardedBy("mLock") 76 private final SparseIntArray mDisplayToProfileUserMapping = new SparseIntArray(); 77 78 /** key: profile user id, value: display id */ 79 @GuardedBy("mLock") 80 private final SparseIntArray mDefaultDisplayForProfileUser = new SparseIntArray(); 81 82 /** key: Activity, value: TaskDisplayAreaWrapper */ 83 @GuardedBy("mLock") 84 private final ArrayMap<ComponentName, TaskDisplayAreaWrapper> mPersistentActivities = 85 new ArrayMap<>(); 86 CarLaunchParamsModifierUpdatableImpl(CarLaunchParamsModifierInterface builtin)87 public CarLaunchParamsModifierUpdatableImpl(CarLaunchParamsModifierInterface builtin) { 88 mBuiltin = builtin; 89 } 90 getDisplayListener()91 public DisplayManager.DisplayListener getDisplayListener() { 92 return mDisplayListener; 93 } 94 95 private final DisplayManager.DisplayListener mDisplayListener = 96 new DisplayManager.DisplayListener() { 97 @Override 98 public void onDisplayAdded(int displayId) { 99 // ignore. car service should update whiltelist. 100 } 101 102 @Override 103 public void onDisplayRemoved(int displayId) { 104 synchronized (mLock) { 105 mPassengerDisplays.remove(Integer.valueOf(displayId)); 106 updateProfileUserConfigForDisplayRemovalLocked(displayId); 107 } 108 } 109 110 @Override 111 public void onDisplayChanged(int displayId) { 112 // ignore 113 } 114 }; 115 116 @GuardedBy("mLock") updateProfileUserConfigForDisplayRemovalLocked(int displayId)117 private void updateProfileUserConfigForDisplayRemovalLocked(int displayId) { 118 mDisplayToProfileUserMapping.delete(displayId); 119 int i = mDefaultDisplayForProfileUser.indexOfValue(displayId); 120 if (i >= 0) { 121 mDefaultDisplayForProfileUser.removeAt(i); 122 } 123 } 124 125 @Override handleUserVisibilityChanged(int userId, boolean visible)126 public void handleUserVisibilityChanged(int userId, boolean visible) { 127 synchronized (mLock) { 128 if (DBG) { 129 Slogf.d(TAG, "handleUserVisibilityChanged user=%d, visible=%b", 130 userId, visible); 131 } 132 if (userId != mDriverUser || visible) { 133 return; 134 } 135 int currentOrTargetUserId = getCurrentOrTargetUserId(); 136 maySwitchCurrentDriver(currentOrTargetUserId); 137 } 138 } 139 getCurrentOrTargetUserId()140 private int getCurrentOrTargetUserId() { 141 Pair<Integer, Integer> currentAndTargetUserIds = mBuiltin.getCurrentAndTargetUserIds(); 142 int currentUserId = currentAndTargetUserIds.first; 143 int targetUserId = currentAndTargetUserIds.second; 144 int currentOrTargetUserId = targetUserId != USER_NULL ? targetUserId : currentUserId; 145 return currentOrTargetUserId; 146 } 147 148 /** Notifies user switching. */ handleCurrentUserSwitching(@serIdInt int newUserId)149 public void handleCurrentUserSwitching(@UserIdInt int newUserId) { 150 if (DBG) Slogf.d(TAG, "handleCurrentUserSwitching user=%d", newUserId); 151 maySwitchCurrentDriver(newUserId); 152 } 153 maySwitchCurrentDriver(int userId)154 private void maySwitchCurrentDriver(int userId) { 155 synchronized (mLock) { 156 if (DBG) { 157 Slogf.d(TAG, "maySwitchCurrentDriver old=%d, new=%d", mDriverUser, userId); 158 } 159 if (mDriverUser == userId) { 160 return; 161 } 162 mDriverUser = userId; 163 mDefaultDisplayForProfileUser.clear(); 164 mDisplayToProfileUserMapping.clear(); 165 } 166 } 167 168 /** Notifies user starting. */ handleUserStarting(int startingUser)169 public void handleUserStarting(int startingUser) { 170 if (DBG) Slogf.d(TAG, "handleUserStarting user=%d", startingUser); 171 // Do nothing 172 } 173 174 /** Notifies user stopped. */ handleUserStopped(@serIdInt int stoppedUser)175 public void handleUserStopped(@UserIdInt int stoppedUser) { 176 if (DBG) Slogf.d(TAG, "handleUserStopped user=%d", stoppedUser); 177 // Note that the current user is never stopped. It always takes switching into 178 // non-current user before stopping the user. 179 synchronized (mLock) { 180 removeUserFromAllowlistsLocked(stoppedUser); 181 } 182 } 183 184 @GuardedBy("mLock") removeUserFromAllowlistsLocked(int userId)185 private void removeUserFromAllowlistsLocked(int userId) { 186 for (int i = mDisplayToProfileUserMapping.size() - 1; i >= 0; i--) { 187 if (mDisplayToProfileUserMapping.valueAt(i) == userId) { 188 mDisplayToProfileUserMapping.removeAt(i); 189 } 190 } 191 mDefaultDisplayForProfileUser.delete(userId); 192 } 193 194 /** 195 * Sets display allowlist for the {@code userId}. For passenger user, activity will be always 196 * launched to a display in the allowlist. If requested display is not in the allowlist, the 1st 197 * display in the allowlist will be selected as target display. 198 * 199 * <p>The allowlist is kept only for profile user. Assigning the current user unassigns users 200 * for the given displays. 201 */ setDisplayAllowListForUser(@serIdInt int userId, int[] displayIds)202 public void setDisplayAllowListForUser(@UserIdInt int userId, int[] displayIds) { 203 if (DBG) { 204 Slogf.d(TAG, "setDisplayAllowlistForUser userId:%d displays:%s", 205 userId, Arrays.toString(displayIds)); 206 } 207 synchronized (mLock) { 208 for (int displayId : displayIds) { 209 if (!mPassengerDisplays.contains(displayId)) { 210 Slogf.w(TAG, "setDisplayAllowlistForUser called with display:%d" 211 + " not in passenger display list:%s", displayId, mPassengerDisplays); 212 continue; 213 } 214 if (userId == mDriverUser) { 215 mDisplayToProfileUserMapping.delete(displayId); 216 } else { 217 mDisplayToProfileUserMapping.put(displayId, userId); 218 } 219 // now the display cannot be a default display for other user 220 int i = mDefaultDisplayForProfileUser.indexOfValue(displayId); 221 if (i >= 0) { 222 mDefaultDisplayForProfileUser.removeAt(i); 223 } 224 } 225 if (displayIds.length > 0) { 226 mDefaultDisplayForProfileUser.put(userId, displayIds[0]); 227 } else { 228 removeUserFromAllowlistsLocked(userId); 229 } 230 } 231 } 232 233 /** 234 * Sets displays assigned to passenger. All other displays will be treated as assigned to 235 * driver. 236 * 237 * <p>The 1st display in the array will be considered as a default display to assign 238 * for any non-driver user if there is no display assigned for the user. </p> 239 */ setPassengerDisplays(int[] displayIdsForPassenger)240 public void setPassengerDisplays(int[] displayIdsForPassenger) { 241 if (DBG) { 242 Slogf.d(TAG, "setPassengerDisplays displays:%s", 243 Arrays.toString(displayIdsForPassenger)); 244 } 245 synchronized (mLock) { 246 for (int id : displayIdsForPassenger) { 247 mPassengerDisplays.remove(Integer.valueOf(id)); 248 } 249 // handle removed displays 250 for (int i = 0; i < mPassengerDisplays.size(); i++) { 251 int displayId = mPassengerDisplays.get(i); 252 updateProfileUserConfigForDisplayRemovalLocked(displayId); 253 } 254 mPassengerDisplays.clear(); 255 mPassengerDisplays.ensureCapacity(displayIdsForPassenger.length); 256 for (int id : displayIdsForPassenger) { 257 mPassengerDisplays.add(id); 258 } 259 } 260 } 261 262 /** 263 * Calculates {@code outParams} based on the given arguments. 264 * See {@code LaunchParamsController.LaunchParamsModifier.onCalculate()} for the detail. 265 */ calculate(CalculateParams params)266 public int calculate(CalculateParams params) { 267 TaskWrapper task = params.getTask(); 268 ActivityRecordWrapper activity = params.getActivity(); 269 ActivityRecordWrapper source = params.getSource(); 270 ActivityOptionsWrapper options = params.getOptions(); 271 RequestWrapper request = params.getRequest(); 272 LaunchParamsWrapper currentParams = params.getCurrentParams(); 273 LaunchParamsWrapper outParams = params.getOutParams(); 274 275 int userId; 276 if (task != null) { 277 userId = task.getUserId(); 278 } else if (activity != null) { 279 userId = activity.getUserId(); 280 } else { 281 Slogf.w(TAG, "onCalculate, cannot decide user"); 282 return LaunchParamsWrapper.RESULT_SKIP; 283 } 284 // DisplayArea where user wants to launch the Activity. 285 TaskDisplayAreaWrapper originalDisplayArea = currentParams.getPreferredTaskDisplayArea(); 286 // DisplayArea where CarLaunchParamsModifier targets to launch the Activity. 287 TaskDisplayAreaWrapper targetDisplayArea = null; 288 ComponentName activityName = null; 289 if (activity != null) { 290 activityName = activity.getComponentName(); 291 } 292 if (DBG) { 293 Slogf.d(TAG, "onCalculate, userId:%d original displayArea:%s actvity:%s options:%s", 294 userId, originalDisplayArea, activityName, options); 295 } 296 decision: 297 synchronized (mLock) { 298 // If originalDisplayArea is set, respect that before ActivityOptions check. 299 if (originalDisplayArea == null) { 300 if (options != null) { 301 originalDisplayArea = options.getLaunchTaskDisplayArea(); 302 if (originalDisplayArea == null) { 303 originalDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( 304 options.getOptions().getLaunchDisplayId()); 305 } 306 } 307 } 308 if (mPersistentActivities.containsKey(activityName)) { 309 targetDisplayArea = mPersistentActivities.get(activityName); 310 } else if (originalDisplayArea == null 311 && task == null // launching as a new task 312 && source != null && !source.isDisplayTrusted() 313 && !source.allowingEmbedded()) { 314 if (DBG) { 315 Slogf.d(TAG, "Disallow launch on virtual display for not-embedded activity."); 316 } 317 targetDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( 318 Display.DEFAULT_DISPLAY); 319 } 320 if (userId == mDriverUser) { 321 // Respect the existing DisplayArea. 322 if (DBG) Slogf.d(TAG, "Skip the further check for Driver"); 323 break decision; 324 } 325 if (userId == UserManagerHelper.USER_SYSTEM) { 326 // This will be only allowed if it has FLAG_SHOW_FOR_ALL_USERS. 327 // The flag is not immediately accessible here so skip the check. 328 // But other WM policy will enforce it. 329 if (DBG) Slogf.d(TAG, "Skip the further check for SystemUser"); 330 break decision; 331 } 332 // Now user is a passenger. 333 if (mPassengerDisplays.isEmpty()) { 334 // No displays for passengers. This could be old user and do not do anything. 335 if (DBG) Slogf.d(TAG, "Skip the further check for no PassengerDisplays"); 336 break decision; 337 } 338 if (targetDisplayArea == null) { 339 if (originalDisplayArea != null) { 340 targetDisplayArea = originalDisplayArea; 341 } else { 342 targetDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay( 343 Display.DEFAULT_DISPLAY); 344 } 345 } 346 Display display = targetDisplayArea.getDisplay(); 347 if ((display.getFlags() & Display.FLAG_PRIVATE) != 0) { 348 // private display should follow its own restriction rule. 349 if (DBG) Slogf.d(TAG, "Skip the further check for the private display"); 350 break decision; 351 } 352 if (DisplayHelper.getType(display) == DisplayHelper.TYPE_VIRTUAL) { 353 // TODO(b/132903422) : We need to update this after the bug is resolved. 354 // For now, don't change anything. 355 if (DBG) Slogf.d(TAG, "Skip the further check for the virtual display"); 356 break decision; 357 } 358 int userForDisplay = getUserForDisplayLocked(display.getDisplayId()); 359 if (userForDisplay == userId) { 360 if (DBG) Slogf.d(TAG, "The display is assigned for the user"); 361 break decision; 362 } 363 targetDisplayArea = getAlternativeDisplayAreaForPassengerLocked( 364 userId, activity, request); 365 } 366 if (targetDisplayArea != null && originalDisplayArea != targetDisplayArea) { 367 Slogf.i(TAG, "Changed launching display, user:%d requested display area:%s" 368 + " target display area:%s", userId, originalDisplayArea, targetDisplayArea); 369 outParams.setPreferredTaskDisplayArea(targetDisplayArea); 370 if (options != null 371 && options.getLaunchWindowingMode() 372 != ActivityOptionsWrapper.WINDOWING_MODE_UNDEFINED) { 373 outParams.setWindowingMode(options.getLaunchWindowingMode()); 374 } 375 return LaunchParamsWrapper.RESULT_DONE; 376 } else { 377 return LaunchParamsWrapper.RESULT_SKIP; 378 } 379 } 380 381 @GuardedBy("mLock") getUserForDisplayLocked(int displayId)382 private int getUserForDisplayLocked(int displayId) { 383 int userForDisplay = mDisplayToProfileUserMapping.get(displayId, 384 UserManagerHelper.USER_NULL); 385 if (userForDisplay != UserManagerHelper.USER_NULL) { 386 return userForDisplay; 387 } 388 return mBuiltin.getUserAssignedToDisplay(displayId); 389 } 390 391 @GuardedBy("mLock") 392 @Nullable getAlternativeDisplayAreaForPassengerLocked(int userId, @NonNull ActivityRecordWrapper activtyRecord, @Nullable RequestWrapper request)393 private TaskDisplayAreaWrapper getAlternativeDisplayAreaForPassengerLocked(int userId, 394 @NonNull ActivityRecordWrapper activtyRecord, @Nullable RequestWrapper request) { 395 if (DBG) Slogf.d(TAG, "getAlternativeDisplayAreaForPassengerLocked:%d", userId); 396 List<TaskDisplayAreaWrapper> fallbacks = mBuiltin.getFallbackDisplayAreasForActivity( 397 activtyRecord, request); 398 for (int i = 0, size = fallbacks.size(); i < size; ++i) { 399 TaskDisplayAreaWrapper fallbackTda = fallbacks.get(i); 400 int userForDisplay = getUserIdForDisplayLocked(fallbackTda.getDisplay().getDisplayId()); 401 if (userForDisplay == userId) { 402 return fallbackTda; 403 } 404 } 405 return fallbackDisplayAreaForUserLocked(userId); 406 } 407 408 /** 409 * Returns {@code userId} who is allowed to use the given {@code displayId}, or 410 * {@code UserHandle.USER_NULL} if the display doesn't exist in the mapping. 411 */ 412 @GuardedBy("mLock") getUserIdForDisplayLocked(int displayId)413 private int getUserIdForDisplayLocked(int displayId) { 414 return mDisplayToProfileUserMapping.get(displayId, UserManagerHelper.USER_NULL); 415 } 416 417 /** 418 * Return a {@link TaskDisplayAreaWrapper} that can be used if a source display area is 419 * not found. First check the default display for the user. If it is absent select 420 * the first passenger display if present. If both are absent return {@code null} 421 * 422 * @param userId ID of the active user 423 * @return {@link TaskDisplayAreaWrapper} that is recommended when a display area is 424 * not specified 425 */ 426 @GuardedBy("mLock") 427 @Nullable fallbackDisplayAreaForUserLocked(@serIdInt int userId)428 private TaskDisplayAreaWrapper fallbackDisplayAreaForUserLocked(@UserIdInt int userId) { 429 int displayIdForUserProfile = mDefaultDisplayForProfileUser.get(userId, 430 Display.INVALID_DISPLAY); 431 if (displayIdForUserProfile != Display.INVALID_DISPLAY) { 432 int displayId = mDefaultDisplayForProfileUser.get(userId); 433 return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId); 434 } 435 int displayId = mBuiltin.getMainDisplayAssignedToUser(userId); 436 if (displayId != Display.INVALID_DISPLAY) { 437 return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId); 438 } 439 440 if (!mPassengerDisplays.isEmpty()) { 441 displayId = mPassengerDisplays.get(0); 442 if (DBG) { 443 Slogf.d(TAG, "fallbackDisplayAreaForUserLocked: userId=%d, displayId=%d", 444 userId, displayId); 445 } 446 return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId); 447 } 448 return null; 449 } 450 451 /** 452 * See {@link CarActivityManager#setPersistentActivity(android.content.ComponentName,int, int)} 453 */ setPersistentActivity(ComponentName activity, int displayId, int featureId)454 public int setPersistentActivity(ComponentName activity, int displayId, int featureId) { 455 if (DBG) { 456 Slogf.d(TAG, "setPersistentActivity: activity=%s, displayId=%d, featureId=%d", 457 activity, displayId, featureId); 458 } 459 if (featureId == DisplayAreaOrganizerHelper.FEATURE_UNDEFINED) { 460 synchronized (mLock) { 461 TaskDisplayAreaWrapper removed = mPersistentActivities.remove(activity); 462 if (removed == null) { 463 throw new ServiceSpecificException( 464 CarActivityManager.ERROR_CODE_ACTIVITY_NOT_FOUND, 465 "Failed to remove " + activity.toShortString()); 466 } 467 return CarActivityManager.RESULT_SUCCESS; 468 } 469 } 470 TaskDisplayAreaWrapper tda = mBuiltin.findTaskDisplayArea(displayId, featureId); 471 if (tda == null) { 472 throw new IllegalArgumentException("Unknown display=" + displayId 473 + " or feature=" + featureId); 474 } 475 synchronized (mLock) { 476 mPersistentActivities.put(activity, tda); 477 } 478 return CarActivityManager.RESULT_SUCCESS; 479 } 480 481 /** 482 * Dump {code CarLaunchParamsModifierUpdatableImpl#mPersistentActivities} 483 */ dump(IndentingPrintWriter writer)484 public void dump(IndentingPrintWriter writer) { 485 writer.println(TAG); 486 writer.increaseIndent(); 487 writer.println("Persistent Activities:"); 488 writer.increaseIndent(); 489 synchronized (mLock) { 490 if (mPersistentActivities.size() == 0) { 491 writer.println("No activity persisted on a task display area"); 492 } else { 493 for (int i = 0; i < mPersistentActivities.size(); i++) { 494 TaskDisplayAreaWrapper taskDisplayAreaWrapper = 495 mPersistentActivities.valueAt(i); 496 writer.println( 497 "Activity name: " + mPersistentActivities.keyAt(i) + " - Display ID: " 498 + taskDisplayAreaWrapper.getDisplay().getDisplayId() 499 + " , Feature ID: " + taskDisplayAreaWrapper.getFeatureId()); 500 } 501 } 502 } 503 writer.decreaseIndent(); 504 writer.decreaseIndent(); 505 } 506 } 507