• 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 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