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