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