1 /* 2 * Copyright (C) 2019 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 com.android.server.wm.ActivityStarter.Request; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.ActivityOptions; 24 import android.app.ActivityTaskManager; 25 import android.content.ActivityNotFoundException; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.pm.ActivityInfo; 29 import android.hardware.display.DisplayManager; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.ServiceSpecificException; 33 import android.os.UserHandle; 34 import android.util.ArrayMap; 35 import android.util.Slog; 36 import android.util.SparseIntArray; 37 import android.view.Display; 38 import android.window.DisplayAreaOrganizer; 39 import android.window.WindowContainerToken; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import java.util.ArrayList; 45 import java.util.Collections; 46 import java.util.List; 47 48 /** 49 * Class to control the assignment of a display for Car while launching a Activity. 50 * 51 * <p>This one controls which displays users are allowed to launch. 52 * The policy should be passed from car service through 53 * {@link com.android.internal.car.ICarServiceHelper} binder interfaces. If no policy is set, 54 * this module will not change anything for launch process.</p> 55 * 56 * <p> The policy can only affect which display passenger users can use. Current user, assumed 57 * to be a driver user, is allowed to launch any display always.</p> 58 */ 59 public final class CarLaunchParamsModifier implements LaunchParamsController.LaunchParamsModifier { 60 61 private static final String TAG = "CAR.LAUNCH"; 62 private static final boolean DBG = false; 63 64 // The following constants come from android.car.app.CarActivityManager. 65 /** Indicates that the operation was successful. */ 66 @VisibleForTesting static final int RESULT_SUCCESS = 0; 67 /** 68 * Internal error code for throwing {@link ActivityNotFoundException} from service. 69 * @hide 70 */ 71 public static final int ERROR_CODE_ACTIVITY_NOT_FOUND = -101; 72 73 private final Context mContext; 74 75 private DisplayManager mDisplayManager; // set only from init() 76 private ActivityTaskManagerService mAtm; // set only from init() 77 78 private final Object mLock = new Object(); 79 80 // Always start with USER_SYSTEM as the timing of handleCurrentUserSwitching(USER_SYSTEM) is not 81 // guaranteed to be earler than 1st Activity launch. 82 @GuardedBy("mLock") 83 private int mCurrentDriverUser = UserHandle.USER_SYSTEM; 84 85 // TODO: Switch from tracking displays to tracking display areas instead 86 /** 87 * This one is for holding all passenger (=profile user) displays which are mostly static unless 88 * displays are added / removed. Note that {@link #mDisplayToProfileUserMapping} can be empty 89 * while user is assigned and that cannot always tell if specific display is for driver or not. 90 */ 91 @GuardedBy("mLock") 92 private final ArrayList<Integer> mPassengerDisplays = new ArrayList<>(); 93 94 /** key: display id, value: profile user id */ 95 @GuardedBy("mLock") 96 private final SparseIntArray mDisplayToProfileUserMapping = new SparseIntArray(); 97 98 /** key: profile user id, value: display id */ 99 @GuardedBy("mLock") 100 private final SparseIntArray mDefaultDisplayForProfileUser = new SparseIntArray(); 101 102 @GuardedBy("mLock") 103 private boolean mIsSourcePreferred; 104 105 @GuardedBy("mLock") 106 private List<ComponentName> mSourcePreferredComponents; 107 108 @GuardedBy("mLock") 109 private final ArrayMap<ComponentName, TaskDisplayArea> mPersistentActivities = new ArrayMap<>(); 110 111 @VisibleForTesting 112 final DisplayManager.DisplayListener mDisplayListener = 113 new DisplayManager.DisplayListener() { 114 @Override 115 public void onDisplayAdded(int displayId) { 116 // ignore. car service should update whiltelist. 117 } 118 119 @Override 120 public void onDisplayRemoved(int displayId) { 121 synchronized (mLock) { 122 mPassengerDisplays.remove(Integer.valueOf(displayId)); 123 updateProfileUserConfigForDisplayRemovalLocked(displayId); 124 } 125 } 126 127 @Override 128 public void onDisplayChanged(int displayId) { 129 // ignore 130 } 131 }; 132 updateProfileUserConfigForDisplayRemovalLocked(int displayId)133 private void updateProfileUserConfigForDisplayRemovalLocked(int displayId) { 134 mDisplayToProfileUserMapping.delete(displayId); 135 int i = mDefaultDisplayForProfileUser.indexOfValue(displayId); 136 if (i >= 0) { 137 mDefaultDisplayForProfileUser.removeAt(i); 138 } 139 } 140 141 /** Constructor. Can be constructed any time. */ CarLaunchParamsModifier(Context context)142 public CarLaunchParamsModifier(Context context) { 143 // This can be very early stage. So postpone interaction with other system until init. 144 mContext = context; 145 } 146 147 /** 148 * Initializes all internal stuffs. This should be called only after ATMS, DisplayManagerService 149 * are ready. 150 */ init()151 public void init() { 152 mAtm = (ActivityTaskManagerService) ActivityTaskManager.getService(); 153 LaunchParamsController controller = mAtm.mTaskSupervisor.getLaunchParamsController(); 154 controller.registerModifier(this); 155 mDisplayManager = mContext.getSystemService(DisplayManager.class); 156 mDisplayManager.registerDisplayListener(mDisplayListener, 157 new Handler(Looper.getMainLooper())); 158 } 159 160 /** 161 * Sets sourcePreferred configuration. When sourcePreferred is enabled and there is no pre- 162 * assigned display for the Activity, CarLauncherParamsModifier will launch the Activity in 163 * the display of the source. When sourcePreferredComponents isn't null the sourcePreferred 164 * is applied for the sourcePreferredComponents only. 165 * 166 * @param enableSourcePreferred whether to enable sourcePreferred mode 167 * @param sourcePreferredComponents null for all components, or the list of components to apply 168 */ setSourcePreferredComponents(boolean enableSourcePreferred, @Nullable List<ComponentName> sourcePreferredComponents)169 public void setSourcePreferredComponents(boolean enableSourcePreferred, 170 @Nullable List<ComponentName> sourcePreferredComponents) { 171 synchronized (mLock) { 172 mIsSourcePreferred = enableSourcePreferred; 173 mSourcePreferredComponents = sourcePreferredComponents; 174 if (mSourcePreferredComponents != null) { 175 Collections.sort(mSourcePreferredComponents); 176 } 177 } 178 } 179 180 /** Notifies user switching. */ handleCurrentUserSwitching(int newUserId)181 public void handleCurrentUserSwitching(int newUserId) { 182 synchronized (mLock) { 183 mCurrentDriverUser = newUserId; 184 mDefaultDisplayForProfileUser.clear(); 185 mDisplayToProfileUserMapping.clear(); 186 } 187 } 188 removeUserFromAllowlistsLocked(int userId)189 private void removeUserFromAllowlistsLocked(int userId) { 190 for (int i = mDisplayToProfileUserMapping.size() - 1; i >= 0; i--) { 191 if (mDisplayToProfileUserMapping.valueAt(i) == userId) { 192 mDisplayToProfileUserMapping.removeAt(i); 193 } 194 } 195 mDefaultDisplayForProfileUser.delete(userId); 196 } 197 198 /** Notifies user stopped. */ handleUserStopped(int stoppedUser)199 public void handleUserStopped(int stoppedUser) { 200 // Note that the current user is never stopped. It always takes switching into 201 // non-current user before stopping the user. 202 synchronized (mLock) { 203 removeUserFromAllowlistsLocked(stoppedUser); 204 } 205 } 206 207 /** 208 * Sets display allowlist for the userId. For passenger user, activity will be always launched 209 * to a display in the allowlist. If requested display is not in the allowlist, the 1st display 210 * in the allowlist will be selected as target display. 211 * 212 * <p>The allowlist is kept only for profile user. Assigning the current user unassigns users 213 * for the given displays. 214 */ setDisplayAllowListForUser(int userId, int[] displayIds)215 public void setDisplayAllowListForUser(int userId, int[] displayIds) { 216 if (DBG) { 217 Slog.d(TAG, "setDisplayAllowlistForUser userId:" + userId 218 + " displays:" + displayIds); 219 } 220 synchronized (mLock) { 221 for (int displayId : displayIds) { 222 if (!mPassengerDisplays.contains(displayId)) { 223 Slog.w(TAG, "setDisplayAllowlistForUser called with display:" + displayId 224 + " not in passenger display list:" + mPassengerDisplays); 225 continue; 226 } 227 if (userId == mCurrentDriverUser) { 228 mDisplayToProfileUserMapping.delete(displayId); 229 } else { 230 mDisplayToProfileUserMapping.put(displayId, userId); 231 } 232 // now the display cannot be a default display for other user 233 int i = mDefaultDisplayForProfileUser.indexOfValue(displayId); 234 if (i >= 0) { 235 mDefaultDisplayForProfileUser.removeAt(i); 236 } 237 } 238 if (displayIds.length > 0) { 239 mDefaultDisplayForProfileUser.put(userId, displayIds[0]); 240 } else { 241 removeUserFromAllowlistsLocked(userId); 242 } 243 } 244 } 245 246 /** 247 * Sets displays assigned to passenger. All other displays will be treated as assigned to 248 * driver. 249 * 250 * <p>The 1st display in the array will be considered as a default display to assign 251 * for any non-driver user if there is no display assigned for the user. </p> 252 */ setPassengerDisplays(int[] displayIdsForPassenger)253 public void setPassengerDisplays(int[] displayIdsForPassenger) { 254 if (DBG) { 255 Slog.d(TAG, "setPassengerDisplays displays:" + displayIdsForPassenger); 256 } 257 synchronized (mLock) { 258 for (int id : displayIdsForPassenger) { 259 mPassengerDisplays.remove(Integer.valueOf(id)); 260 } 261 // handle removed displays 262 for (int i = 0; i < mPassengerDisplays.size(); i++) { 263 int displayId = mPassengerDisplays.get(i); 264 updateProfileUserConfigForDisplayRemovalLocked(displayId); 265 } 266 mPassengerDisplays.clear(); 267 mPassengerDisplays.ensureCapacity(displayIdsForPassenger.length); 268 for (int id : displayIdsForPassenger) { 269 mPassengerDisplays.add(id); 270 } 271 } 272 } 273 274 /** 275 * Decides display to assign while an Activity is launched. 276 * 277 * <p>For current user (=driver), launching to any display is allowed as long as system 278 * allows it.</p> 279 * 280 * <p>For private display, do not change anything as private display has its own logic.</p> 281 * 282 * <p>For passenger displays, only run in allowed displays. If requested display is not 283 * allowed, change to the 1st allowed display.</p> 284 */ 285 @Override 286 @Result onCalculate(@ullable Task task, @Nullable ActivityInfo.WindowLayout layout, @Nullable ActivityRecord activity, @Nullable ActivityRecord source, ActivityOptions options, @Nullable Request request, int phase, LaunchParamsController.LaunchParams currentParams, LaunchParamsController.LaunchParams outParams)287 public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout, 288 @Nullable ActivityRecord activity, @Nullable ActivityRecord source, 289 ActivityOptions options, @Nullable Request request, int phase, 290 LaunchParamsController.LaunchParams currentParams, 291 LaunchParamsController.LaunchParams outParams) { 292 int userId; 293 if (task != null) { 294 userId = task.mUserId; 295 } else if (activity != null) { 296 userId = activity.mUserId; 297 } else { 298 Slog.w(TAG, "onCalculate, cannot decide user"); 299 return RESULT_SKIP; 300 } 301 // DisplayArea where user wants to launch the Activity. 302 TaskDisplayArea originalDisplayArea = currentParams.mPreferredTaskDisplayArea; 303 // DisplayArea where CarLaunchParamsModifier targets to launch the Activity. 304 TaskDisplayArea targetDisplayArea = null; 305 if (DBG) { 306 Slog.d(TAG, "onCalculate, userId:" + userId 307 + " original displayArea:" + originalDisplayArea 308 + " ActivityOptions:" + options); 309 } 310 ComponentName activityName = null; 311 if (activity != null && activity.info != null) { 312 activityName = activity.info.getComponentName(); 313 } 314 decision: 315 synchronized (mLock) { 316 // If originalDisplayArea is set, respect that before ActivityOptions check. 317 if (originalDisplayArea == null) { 318 if (options != null) { 319 WindowContainerToken daToken = options.getLaunchTaskDisplayArea(); 320 if (daToken != null) { 321 originalDisplayArea = (TaskDisplayArea) WindowContainer.fromBinder( 322 daToken.asBinder()); 323 } else { 324 int originalDisplayId = options.getLaunchDisplayId(); 325 if (originalDisplayId != Display.INVALID_DISPLAY) { 326 originalDisplayArea = getDefaultTaskDisplayAreaOnDisplay( 327 originalDisplayId); 328 } 329 } 330 } 331 } 332 if (mPersistentActivities.containsKey(activityName)) { 333 targetDisplayArea = mPersistentActivities.get(activityName); 334 } else if (originalDisplayArea == null // No specified DA to launch the Activity 335 && mIsSourcePreferred && source != null 336 && (mSourcePreferredComponents == null || Collections.binarySearch( 337 mSourcePreferredComponents, activity.info.getComponentName()) >= 0)) { 338 targetDisplayArea = source.noDisplay ? source.mHandoverTaskDisplayArea 339 : source.getDisplayArea(); 340 } else if (originalDisplayArea == null 341 && task == null // launching as a new task 342 && source != null && !source.getDisplayContent().isTrusted() 343 && ((activity.info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0)) { 344 if (DBG) { 345 Slog.d(TAG, "Disallow launch on virtual display for not-embedded activity."); 346 } 347 targetDisplayArea = getDefaultTaskDisplayAreaOnDisplay(Display.DEFAULT_DISPLAY); 348 } 349 if (userId == mCurrentDriverUser) { 350 // Respect the existing DisplayArea. 351 break decision; 352 } 353 if (userId == UserHandle.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 break decision; 358 } 359 // Now user is a passenger. 360 if (mPassengerDisplays.isEmpty()) { 361 // No displays for passengers. This could be old user and do not do anything. 362 break decision; 363 } 364 if (targetDisplayArea == null) { 365 if (originalDisplayArea != null) { 366 targetDisplayArea = originalDisplayArea; 367 } else { 368 targetDisplayArea = getDefaultTaskDisplayAreaOnDisplay(Display.DEFAULT_DISPLAY); 369 } 370 } 371 Display display = targetDisplayArea.mDisplayContent.getDisplay(); 372 if ((display.getFlags() & Display.FLAG_PRIVATE) != 0) { 373 // private display should follow its own restriction rule. 374 break decision; 375 } 376 if (display.getType() == Display.TYPE_VIRTUAL) { 377 // TODO(b/132903422) : We need to update this after the bug is resolved. 378 // For now, don't change anything. 379 break decision; 380 } 381 int userForDisplay = mDisplayToProfileUserMapping.get(display.getDisplayId(), 382 UserHandle.USER_NULL); 383 if (userForDisplay == userId) { 384 break decision; 385 } 386 targetDisplayArea = getAlternativeDisplayAreaForPassengerLocked( 387 userId, activity, request); 388 } 389 if (targetDisplayArea != null && originalDisplayArea != targetDisplayArea) { 390 Slog.i(TAG, "Changed launching display, user:" + userId 391 + " requested display area:" + originalDisplayArea 392 + " target display area:" + targetDisplayArea); 393 outParams.mPreferredTaskDisplayArea = targetDisplayArea; 394 return RESULT_DONE; 395 } else { 396 return RESULT_SKIP; 397 } 398 } 399 400 @Nullable getAlternativeDisplayAreaForPassengerLocked(int userId, @NonNull ActivityRecord activityRecord, @Nullable Request request)401 private TaskDisplayArea getAlternativeDisplayAreaForPassengerLocked(int userId, 402 @NonNull ActivityRecord activityRecord, @Nullable Request request) { 403 TaskDisplayArea sourceDisplayArea = sourceDisplayArea(userId, activityRecord, request); 404 405 return sourceDisplayArea != null ? sourceDisplayArea : fallbackDisplayArea(userId); 406 } 407 408 @VisibleForTesting 409 @Nullable getDefaultTaskDisplayAreaOnDisplay(int displayId)410 TaskDisplayArea getDefaultTaskDisplayAreaOnDisplay(int displayId) { 411 DisplayContent dc = mAtm.mRootWindowContainer.getDisplayContentOrCreate(displayId); 412 if (dc == null) { 413 return null; 414 } 415 return dc.getDefaultTaskDisplayArea(); 416 } 417 418 /** 419 * Calculates the {@link TaskDisplayArea} for the source of the request. The source is 420 * calculated implicitly from the request or the activity record. 421 * 422 * @param userId ID of the current active user 423 * @param activityRecord {@link ActivityRecord} that is to be shown 424 * @param request {@link Request} data for showing the {@link ActivityRecord} 425 * @return {@link TaskDisplayArea} First non {@code null} candidate display area that is allowed 426 * for the user. It is allowed if the display has been added to the profile mapping. 427 */ 428 @Nullable sourceDisplayArea(int userId, @NonNull ActivityRecord activityRecord, @Nullable Request request)429 private TaskDisplayArea sourceDisplayArea(int userId, @NonNull ActivityRecord activityRecord, 430 @Nullable Request request) { 431 List<WindowProcessController> candidateControllers = candidateControllers(activityRecord, 432 request); 433 434 for (int i = 0; i < candidateControllers.size(); i++) { 435 WindowProcessController controller = candidateControllers.get(i); 436 TaskDisplayArea candidate = controller.getTopActivityDisplayArea(); 437 int displayId = candidate != null ? candidate.getDisplayId() : Display.INVALID_DISPLAY; 438 int userForDisplay = mDisplayToProfileUserMapping.get(displayId, UserHandle.USER_NULL); 439 if (userForDisplay == userId) { 440 return candidate; 441 } 442 } 443 return null; 444 } 445 446 /** 447 * Calculates a list of {@link WindowProcessController} that can calculate the 448 * {@link TaskDisplayArea} to house the {@link ActivityRecord}. Controllers are calculated since 449 * calculating the display can be expensive. The list is ordered in the 450 * following way 451 * <ol> 452 * <li>Controller for the activity record from the process name and app uid</li> 453 * <li>Controller for the activity that is launching the given record</li> 454 * <li>Controller for the actual process that is launching the record</li> 455 * </ol> 456 * 457 * @param activityRecord {@link ActivityRecord} that is to be shown 458 * @param request {@link Request} data for showing the {@link ActivityRecord} 459 * @return {@link List} of {@link WindowProcessController} ordered by preference to be shown 460 */ candidateControllers( @onNull ActivityRecord activityRecord, @Nullable Request request)461 private List<WindowProcessController> candidateControllers( 462 @NonNull ActivityRecord activityRecord, @Nullable Request request) { 463 WindowProcessController firstController = mAtm.getProcessController( 464 activityRecord.getProcessName(), activityRecord.getUid()); 465 466 WindowProcessController secondController = mAtm.getProcessController( 467 activityRecord.getLaunchedFromPid(), activityRecord.getLaunchedFromUid()); 468 469 WindowProcessController thirdController = request == null ? null : 470 mAtm.getProcessController(request.realCallingPid, request.realCallingUid); 471 472 List<WindowProcessController> candidates = new ArrayList<>(3); 473 474 if (firstController != null) { 475 candidates.add(firstController); 476 } 477 if (secondController != null) { 478 candidates.add(secondController); 479 } 480 if (thirdController != null) { 481 candidates.add(thirdController); 482 } 483 484 return candidates; 485 } 486 487 /** 488 * Return a {@link TaskDisplayArea} that can be used if a source display area is not found. 489 * First check the default display for the user. If it is absent select the first passenger 490 * display if present. If both are absent return {@code null} 491 * 492 * @param userId ID of the active user 493 * @return {@link TaskDisplayArea} that is recommended when a display area is not specified 494 */ 495 @Nullable fallbackDisplayArea(int userId)496 private TaskDisplayArea fallbackDisplayArea(int userId) { 497 int displayIdForUserProfile = mDefaultDisplayForProfileUser.get(userId, 498 Display.INVALID_DISPLAY); 499 if (displayIdForUserProfile != Display.INVALID_DISPLAY) { 500 int displayId = mDefaultDisplayForProfileUser.get(userId); 501 return getDefaultTaskDisplayAreaOnDisplay(displayId); 502 } 503 504 if (!mPassengerDisplays.isEmpty()) { 505 int displayId = mPassengerDisplays.get(0); 506 return getDefaultTaskDisplayAreaOnDisplay(displayId); 507 } 508 509 return null; 510 } 511 findTaskDisplayArea(DisplayContent display, int featureId)512 private TaskDisplayArea findTaskDisplayArea(DisplayContent display, int featureId) { 513 return display.getItemFromTaskDisplayAreas( 514 displayArea -> displayArea.mFeatureId == featureId ? displayArea : null); 515 } 516 517 /** 518 * See {@code CarActivityManager#setPersistentActivity(android.content.ComponentName,int, int)} 519 */ setPersistentActivity(ComponentName activity, int displayId, int featureId)520 public int setPersistentActivity(ComponentName activity, int displayId, int featureId) { 521 if (DBG) { 522 Slog.d(TAG, "setPersistentActivity: activity=" + activity + ", displayId=" + displayId 523 + ", featureId=" + featureId); 524 } 525 if (featureId == DisplayAreaOrganizer.FEATURE_UNDEFINED) { 526 synchronized (mLock) { 527 TaskDisplayArea removed = mPersistentActivities.remove(activity); 528 if (removed == null) { 529 throw new ServiceSpecificException( 530 ERROR_CODE_ACTIVITY_NOT_FOUND, 531 "Failed to remove " + activity.toShortString()); 532 } 533 return RESULT_SUCCESS; 534 } 535 } 536 DisplayContent display = mAtm.mRootWindowContainer.getDisplayContentOrCreate(displayId); 537 if (display == null) { 538 throw new IllegalArgumentException("Unknown display=" + displayId); 539 } 540 TaskDisplayArea tda = findTaskDisplayArea(display, featureId); 541 if (tda == null) { 542 throw new IllegalArgumentException("Unknown feature=" + featureId); 543 } 544 synchronized (mLock) { 545 mPersistentActivities.put(activity, tda); 546 } 547 return RESULT_SUCCESS; 548 } 549 } 550