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