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