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