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