• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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