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