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