• 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.car;
18 
19 import static android.car.builtin.view.DisplayHelper.INVALID_PORT;
20 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
21 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
22 import static android.view.Display.STATE_ON;
23 
24 import static com.android.car.CarServiceUtils.getHandlerThread;
25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
26 import static com.android.car.internal.util.VersionUtils.isPlatformVersionAtLeastU;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.annotation.SuppressLint;
31 import android.annotation.UserIdInt;
32 import android.app.ActivityManager;
33 import android.car.Car;
34 import android.car.CarInfoManager;
35 import android.car.CarOccupantZoneManager;
36 import android.car.CarOccupantZoneManager.DisplayTypeEnum;
37 import android.car.CarOccupantZoneManager.OccupantTypeEnum;
38 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
39 import android.car.ICarOccupantZone;
40 import android.car.ICarOccupantZoneCallback;
41 import android.car.PlatformVersion;
42 import android.car.VehicleAreaSeat;
43 import android.car.builtin.util.Slogf;
44 import android.car.builtin.view.DisplayHelper;
45 import android.car.input.CarInputManager;
46 import android.car.media.CarAudioManager;
47 import android.car.user.CarUserManager.UserLifecycleListener;
48 import android.car.user.UserLifecycleEventFilter;
49 import android.content.ComponentName;
50 import android.content.Context;
51 import android.content.pm.PackageManager;
52 import android.content.res.Resources;
53 import android.hardware.display.DisplayManager;
54 import android.os.Binder;
55 import android.os.Handler;
56 import android.os.Looper;
57 import android.os.RemoteCallbackList;
58 import android.os.RemoteException;
59 import android.os.UserHandle;
60 import android.os.UserManager;
61 import android.util.ArrayMap;
62 import android.util.ArraySet;
63 import android.util.Log;
64 import android.util.SparseArray;
65 import android.util.SparseIntArray;
66 import android.view.Display;
67 
68 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
69 import com.android.car.internal.util.IndentingPrintWriter;
70 import com.android.car.internal.util.IntArray;
71 import com.android.car.user.CarUserService;
72 import com.android.car.user.ExperimentalCarUserService;
73 import com.android.car.user.ExperimentalCarUserService.ZoneUserBindingHelper;
74 import com.android.car.user.UserHandleHelper;
75 import com.android.internal.annotations.GuardedBy;
76 import com.android.internal.annotations.VisibleForTesting;
77 import com.android.internal.util.Preconditions;
78 
79 import java.util.ArrayList;
80 import java.util.Arrays;
81 import java.util.List;
82 import java.util.Objects;
83 
84 /**
85  * Service to implement CarOccupantZoneManager API.
86  */
87 public final class CarOccupantZoneService extends ICarOccupantZone.Stub
88         implements CarServiceBase {
89 
90     private static final String TAG = CarLog.tagFor(CarOccupantZoneService.class);
91     private static final String ALL_COMPONENTS = "*";
92     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
93 
94     private static final String HANDLER_THREAD_NAME = "CarOccupantZoneService";
95 
96     private static final int[] EMPTY_INPUT_SUPPORT_TYPES = new int[0];
97 
98     private final Object mLock = new Object();
99     private final Context mContext;
100     private final DisplayManager mDisplayManager;
101     private final UserManager mUserManager;
102 
103     private final boolean mEnableProfileUserAssignmentForMultiDisplay;
104 
105     private boolean mEnableSourcePreferred;
106     private ArrayList<ComponentName> mSourcePreferredComponents;
107 
108     /**
109      * Stores android user id of profile users for the current user.
110      */
111     @GuardedBy("mLock")
112     private final ArraySet<Integer> mProfileUsers = new ArraySet<>();
113 
114     /** key: zone id */
115     @GuardedBy("mLock")
116     private final SparseArray<OccupantZoneInfo> mOccupantsConfig = new SparseArray<>();
117 
118     /**
119      * The config of a display identified by occupant zone id and display type.
120      */
121     public static final class DisplayConfig {
122         public final int displayType;
123         public final int occupantZoneId;
124         public final int[] inputTypes;
125 
DisplayConfig(int displayType, int occupantZoneId, IntArray inputTypes)126         DisplayConfig(int displayType, int occupantZoneId, IntArray inputTypes) {
127             this.displayType = displayType;
128             this.occupantZoneId = occupantZoneId;
129             if (inputTypes == null && Car.getPlatformVersion().isAtLeast(
130                     PlatformVersion.VERSION_CODES.UPSIDE_DOWN_CAKE_0)) {
131                 Slogf.w(TAG, "No input type was defined for displayType:%d "
132                         + " and occupantZoneId:%d", displayType, occupantZoneId);
133             }
134             this.inputTypes = inputTypes == null ? EMPTY_INPUT_SUPPORT_TYPES : inputTypes.toArray();
135         }
136 
137         @Override
toString()138         public String toString() {
139             // do not include type as this is only used for dump
140             StringBuilder b = new StringBuilder(64);
141             b.append("{displayType=");
142             b.append(Integer.toHexString(displayType));
143             b.append(" occupantZoneId=");
144             b.append(occupantZoneId);
145             b.append(" inputTypes=");
146             b.append(Arrays.toString(inputTypes));
147             b.append("}");
148             return b.toString();
149         }
150     }
151 
152     /** key: display port address */
153     @GuardedBy("mLock")
154     private final SparseArray<DisplayConfig> mDisplayPortConfigs = new SparseArray<>();
155 
156     /** key: displayUniqueId */
157     @GuardedBy("mLock")
158     private final ArrayMap<String, DisplayConfig> mDisplayUniqueIdConfigs = new ArrayMap<>();
159 
160     /** key: audio zone id */
161     @GuardedBy("mLock")
162     private final SparseIntArray mAudioZoneIdToOccupantZoneIdMapping = new SparseIntArray();
163 
164     @VisibleForTesting
165     static class DisplayInfo {
166         public final Display display;
167         public final int displayType;
168 
DisplayInfo(Display display, int displayType)169         DisplayInfo(Display display, int displayType) {
170             this.display = display;
171             this.displayType = displayType;
172         }
173 
174         @Override
toString()175         public String toString() {
176             // do not include type as this is only used for dump
177             StringBuilder b = new StringBuilder(64);
178             b.append("{displayId=");
179             b.append(display.getDisplayId());
180             b.append(" displayType=");
181             b.append(displayType);
182             b.append("}");
183             return b.toString();
184         }
185     }
186 
187     @VisibleForTesting
188     static class OccupantConfig {
189         public int userId = CarOccupantZoneManager.INVALID_USER_ID;
190         public final ArrayList<DisplayInfo> displayInfos = new ArrayList<>();
191         public int audioZoneId = CarAudioManager.INVALID_AUDIO_ZONE;
192 
193         @Override
toString()194         public String toString() {
195             // do not include type as this is only used for dump
196             StringBuilder b = new StringBuilder(128);
197             b.append("{userId=");
198             b.append(userId);
199             b.append(" displays=");
200             for (int i = 0; i < displayInfos.size(); i++) {
201                 b.append(displayInfos.get(i).toString());
202             }
203             b.append(" audioZoneId=");
204             if (audioZoneId != CarAudioManager.INVALID_AUDIO_ZONE) {
205                 b.append(audioZoneId);
206             } else {
207                 b.append("none");
208             }
209             b.append("}");
210             return b.toString();
211         }
212     }
213 
214     /** key : zoneId */
215     @GuardedBy("mLock")
216     private final SparseArray<OccupantConfig> mActiveOccupantConfigs = new SparseArray<>();
217 
218     @GuardedBy("mLock")
219     private int mDriverZoneId = OccupantZoneInfo.INVALID_ZONE_ID;
220 
221     @VisibleForTesting
222     final UserLifecycleListener mUserLifecycleListener = event -> {
223         if (DBG) Slogf.d(TAG, "onEvent(%s)", event);
224         handleUserChange();
225     };
226 
227     final ExperimentalCarUserService.PassengerCallback mPassengerCallback =
228             new ExperimentalCarUserService.PassengerCallback() {
229                 @Override
230                 public void onPassengerStarted(@UserIdInt int passengerId, int zoneId) {
231                     handlePassengerStarted();
232                 }
233 
234                 @Override
235                 public void onPassengerStopped(@UserIdInt int passengerId) {
236                     handlePassengerStopped();
237                 }
238             };
239 
240     @VisibleForTesting
241     final DisplayManager.DisplayListener mDisplayListener =
242             new DisplayManager.DisplayListener() {
243                 @Override
244                 public void onDisplayAdded(int displayId) {
245                     handleDisplayChange();
246                 }
247 
248                 @Override
249                 public void onDisplayRemoved(int displayId) {
250                     handleDisplayChange();
251                 }
252 
253                 @Override
254                 public void onDisplayChanged(int displayId) {
255                     // nothing to do
256                 }
257             };
258 
259     private final RemoteCallbackList<ICarOccupantZoneCallback> mClientCallbacks =
260             new RemoteCallbackList<>();
261 
262     @GuardedBy("mLock")
263     private int mDriverSeat = VehicleAreaSeat.SEAT_UNKNOWN;
264     private final UserHandleHelper mUserHandleHelper;
265 
266     final Handler mHandler = new Handler(getHandlerThread(HANDLER_THREAD_NAME).getLooper());
267 
CarOccupantZoneService(Context context)268     public CarOccupantZoneService(Context context) {
269         this(context, context.getSystemService(DisplayManager.class),
270                 context.getSystemService(UserManager.class),
271                 context.getResources().getBoolean(
272                         R.bool.enableProfileUserAssignmentForMultiDisplay)
273                         && context.getPackageManager().hasSystemFeature(
274                                 PackageManager.FEATURE_MANAGED_USERS),
275                 new UserHandleHelper(context, context.getSystemService(UserManager.class)));
276     }
277 
278     @VisibleForTesting
CarOccupantZoneService(Context context, DisplayManager displayManager, UserManager userManager, boolean enableProfileUserAssignmentForMultiDisplay, UserHandleHelper userHandleHelper)279     public CarOccupantZoneService(Context context, DisplayManager displayManager,
280             UserManager userManager, boolean enableProfileUserAssignmentForMultiDisplay,
281             UserHandleHelper userHandleHelper) {
282         mContext = context;
283         mDisplayManager = displayManager;
284         mUserManager = userManager;
285         mEnableProfileUserAssignmentForMultiDisplay = enableProfileUserAssignmentForMultiDisplay;
286         mUserHandleHelper = userHandleHelper;
287     }
288 
289     @Override
init()290     public void init() {
291         // This does not require connection as binder will be passed directly.
292         Car car = new Car(mContext, /* service= */null, /* handler= */ null);
293         CarInfoManager infoManager = new CarInfoManager(car, CarLocalServices.getService(
294                 CarPropertyService.class));
295         int driverSeat = infoManager.getDriverSeat();
296         synchronized (mLock) {
297             mDriverSeat = driverSeat;
298             parseOccupantZoneConfigsLocked();
299             parseDisplayConfigsLocked();
300             handleActiveDisplaysLocked();
301             handleAudioZoneChangesLocked();
302             handleUserChangesLocked();
303         }
304         CarUserService userService = CarLocalServices.getService(CarUserService.class);
305         UserLifecycleEventFilter userEventFilter = new UserLifecycleEventFilter.Builder()
306                 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).addEventType(
307                         USER_LIFECYCLE_EVENT_TYPE_STOPPING).build();
308         userService.addUserLifecycleListener(userEventFilter, mUserLifecycleListener);
309         ExperimentalCarUserService experimentalUserService =
310                 CarLocalServices.getService(ExperimentalCarUserService.class);
311         if (experimentalUserService != null) {
312             experimentalUserService.addPassengerCallback(mPassengerCallback);
313         }
314         mDisplayManager.registerDisplayListener(mDisplayListener,
315                 new Handler(Looper.getMainLooper()));
316         ZoneUserBindingHelper helper = new ZoneUserBindingHelper() {
317             @Override
318             @NonNull
319             public List<OccupantZoneInfo> getOccupantZones(@OccupantTypeEnum int occupantType) {
320                 List<OccupantZoneInfo> zones = new ArrayList<OccupantZoneInfo>();
321                 for (OccupantZoneInfo ozi : getAllOccupantZones()) {
322                     if (ozi.occupantType == occupantType) {
323                         zones.add(ozi);
324                     }
325                 }
326                 return zones;
327             }
328 
329             @Override
330             public boolean assignUserToOccupantZone(@UserIdInt int userId, int zoneId) {
331                 // Check if the user is already assigned to the other zone.
332                 synchronized (mLock) {
333                     int userZoneId = getZoneIdForUserIdLocked(userId);
334                     if (userZoneId != OccupantZoneInfo.INVALID_ZONE_ID
335                             && mActiveOccupantConfigs.keyAt(userZoneId) != zoneId) {
336                         Slogf.w(TAG, "Cannot assign user to two different zones simultaneously");
337                         return false;
338                     }
339                     OccupantConfig zoneConfig = mActiveOccupantConfigs.get(zoneId);
340                     if (zoneConfig == null) {
341                         Slogf.w(TAG, "cannot find the zone(%d)", zoneId);
342                         return false;
343                     }
344                     if (zoneConfig.userId != CarOccupantZoneManager.INVALID_USER_ID
345                             && zoneConfig.userId != userId) {
346                         Slogf.w(TAG, "other user already occupies the zone(%d)", zoneId);
347                         return false;
348                     }
349                     zoneConfig.userId = userId;
350                     return true;
351                 }
352             }
353 
354             @Override
355             public boolean unassignUserFromOccupantZone(@UserIdInt int userId) {
356                 synchronized (mLock) {
357                     for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
358                         OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
359                         if (config.userId == userId) {
360                             config.userId = CarOccupantZoneManager.INVALID_USER_ID;
361                             break;
362                         }
363                     }
364                     return true;
365                 }
366             }
367 
368             @Override
369             public boolean isPassengerDisplayAvailable() {
370                 for (OccupantZoneInfo ozi : getAllOccupantZones()) {
371                     if (getDisplayForOccupant(ozi.zoneId,
372                             CarOccupantZoneManager.DISPLAY_TYPE_MAIN) != Display.INVALID_DISPLAY
373                             && ozi.occupantType != CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
374                         return true;
375                     }
376                 }
377                 return false;
378             }
379         };
380         if (experimentalUserService != null) {
381             experimentalUserService.setZoneUserBindingHelper(helper);
382         }
383 
384         CarServiceHelperWrapper.getInstance().runOnConnection(() -> doSyncWithCarServiceHelper(
385                 /* updateDisplay= */ true, /* updateUser= */ true, /* updateConfig= */ true));
386     }
387 
388     @Override
release()389     public void release() {
390         mDisplayManager.unregisterDisplayListener(mDisplayListener);
391         CarUserService userService = CarLocalServices.getService(CarUserService.class);
392         userService.removeUserLifecycleListener(mUserLifecycleListener);
393         ExperimentalCarUserService experimentalUserService =
394                 CarLocalServices.getService(ExperimentalCarUserService.class);
395         if (experimentalUserService != null) {
396             experimentalUserService.removePassengerCallback(mPassengerCallback);
397         }
398         synchronized (mLock) {
399             mOccupantsConfig.clear();
400             mDisplayPortConfigs.clear();
401             mDisplayUniqueIdConfigs.clear();
402             mAudioZoneIdToOccupantZoneIdMapping.clear();
403             mActiveOccupantConfigs.clear();
404         }
405     }
406 
407     /** Return cloned mOccupantsConfig for testing */
408     @VisibleForTesting
409     @NonNull
getOccupantsConfig()410     public SparseArray<OccupantZoneInfo> getOccupantsConfig() {
411         synchronized (mLock) {
412             return mOccupantsConfig.clone();
413         }
414     }
415 
416     /** Return cloned mDisplayPortConfigs for testing */
417     @VisibleForTesting
418     @NonNull
getDisplayPortConfigs()419     public SparseArray<DisplayConfig> getDisplayPortConfigs() {
420         synchronized (mLock) {
421             return mDisplayPortConfigs.clone();
422         }
423     }
424 
425     /** Return cloned mDisplayUniqueIdConfigs for testing */
426     @VisibleForTesting
427     @NonNull
getDisplayUniqueIdConfigs()428     ArrayMap<String, DisplayConfig> getDisplayUniqueIdConfigs() {
429         synchronized (mLock) {
430             return new ArrayMap<>(mDisplayUniqueIdConfigs);
431         }
432     }
433 
434     /** Return cloned mAudioConfigs for testing */
435     @VisibleForTesting
436     @NonNull
getAudioConfigs()437     SparseIntArray getAudioConfigs() {
438         synchronized (mLock) {
439             return mAudioZoneIdToOccupantZoneIdMapping.clone();
440         }
441     }
442 
443     /** Return cloned mActiveOccupantConfigs for testing */
444     @VisibleForTesting
445     @NonNull
getActiveOccupantConfigs()446     public SparseArray<OccupantConfig> getActiveOccupantConfigs() {
447         synchronized (mLock) {
448             return mActiveOccupantConfigs.clone();
449         }
450     }
451 
452     @Override
453     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)454     public void dump(IndentingPrintWriter writer) {
455         writer.println("*OccupantZoneService*");
456         synchronized (mLock) {
457             writer.println("**mOccupantsConfig**");
458             for (int i = 0; i < mOccupantsConfig.size(); ++i) {
459                 writer.println(" zoneId=" + mOccupantsConfig.keyAt(i)
460                         + " info=" + mOccupantsConfig.valueAt(i));
461             }
462             writer.println("**mDisplayConfigs**");
463             for (int i = 0; i < mDisplayPortConfigs.size(); ++i) {
464                 writer.println(" port=" + mDisplayPortConfigs.keyAt(i)
465                         + " config=" + mDisplayPortConfigs.valueAt(i));
466             }
467             for (int i = 0; i < mDisplayUniqueIdConfigs.size(); ++i) {
468                 writer.println(" uniqueId=" + mDisplayUniqueIdConfigs.keyAt(i)
469                         + " config=" + mDisplayUniqueIdConfigs.valueAt(i));
470             }
471             writer.println("**mAudioZoneIdToOccupantZoneIdMapping**");
472             for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
473                 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
474                 writer.println(" audioZoneId=" + Integer.toHexString(audioZoneId)
475                         + " zoneId=" + mAudioZoneIdToOccupantZoneIdMapping.valueAt(index));
476             }
477             writer.println("**mActiveOccupantConfigs**");
478             for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
479                 writer.println(" zoneId=" + mActiveOccupantConfigs.keyAt(i)
480                         + " config=" + mActiveOccupantConfigs.valueAt(i));
481             }
482             writer.println("mEnableProfileUserAssignmentForMultiDisplay:"
483                     + mEnableProfileUserAssignmentForMultiDisplay);
484             writer.println("mEnableSourcePreferred:"
485                     + mEnableSourcePreferred);
486             writer.append("mSourcePreferredComponents: [");
487             if (mSourcePreferredComponents != null) {
488                 for (int i = 0; i < mSourcePreferredComponents.size(); ++i) {
489                     if (i > 0) writer.append(' ');
490                     writer.append(mSourcePreferredComponents.get(i).toString());
491                 }
492             }
493             writer.println(']');
494             writer.println("hasDriverZone: " + hasDriverZone());
495         }
496     }
497 
498     @Override
getAllOccupantZones()499     public List<OccupantZoneInfo> getAllOccupantZones() {
500         synchronized (mLock) {
501             List<OccupantZoneInfo> infos = new ArrayList<>();
502             for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
503                 int zoneId = mActiveOccupantConfigs.keyAt(i);
504                 // no need for deep copy as OccupantZoneInfo itself is static.
505                 infos.add(mOccupantsConfig.get(zoneId));
506             }
507             return infos;
508         }
509     }
510 
511     @Override
getAllDisplaysForOccupantZone(int occupantZoneId)512     public int[] getAllDisplaysForOccupantZone(int occupantZoneId) {
513         synchronized (mLock) {
514             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
515             if (config == null) {
516                 return new int[0];
517             }
518             int[] displayIds = new int[config.displayInfos.size()];
519             for (int i = 0; i < config.displayInfos.size(); i++) {
520                 displayIds[i] = config.displayInfos.get(i).display.getDisplayId();
521             }
522             return displayIds;
523         }
524     }
525 
526     /**
527      * Checks if all displays for a given OccupantZone are on.
528      *
529      * @param occupantZoneId indicates which OccupantZone's displays to check
530      * @return whether all displays for a given OccupantZone are on
531      */
areDisplaysOnForOccupantZone(int occupantZoneId)532     public boolean areDisplaysOnForOccupantZone(int occupantZoneId) {
533         synchronized (mLock) {
534             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
535             if (config == null) {
536                 return false;
537             }
538             for (int i = 0; i < config.displayInfos.size(); i++) {
539                 if (config.displayInfos.get(i).display.getState() != STATE_ON) {
540                     return false;
541                 }
542             }
543 
544             return true;
545         }
546     }
547 
548     @Override
getDisplayForOccupant(int occupantZoneId, int displayType)549     public int getDisplayForOccupant(int occupantZoneId, int displayType) {
550         synchronized (mLock) {
551             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
552             if (config == null) {
553                 return Display.INVALID_DISPLAY;
554             }
555             for (int i = 0; i < config.displayInfos.size(); i++) {
556                 if (displayType == config.displayInfos.get(i).displayType) {
557                     return config.displayInfos.get(i).display.getDisplayId();
558                 }
559             }
560         }
561         return Display.INVALID_DISPLAY;
562     }
563 
getAllDisplayIdsForDriver(int displayType)564     public IntArray getAllDisplayIdsForDriver(int displayType) {
565         synchronized (mLock) {
566             OccupantConfig config = mActiveOccupantConfigs.get(mDriverZoneId);
567             if (config == null) {
568                 return new IntArray(0);
569             }
570             IntArray displayIds = new IntArray(config.displayInfos.size());
571             for (int i = 0; i < config.displayInfos.size(); i++) {
572                 DisplayInfo displayInfo = config.displayInfos.get(i);
573                 if (displayInfo.displayType == displayType) {
574                     displayIds.add(displayInfo.display.getDisplayId());
575                 }
576             }
577             return displayIds;
578         }
579     }
580 
581     @Override
getDisplayIdForDriver(@isplayTypeEnum int displayType)582     public int getDisplayIdForDriver(@DisplayTypeEnum int displayType) {
583         enforcePermission(Car.ACCESS_PRIVATE_DISPLAY_ID);
584         synchronized (mLock) {
585             int driverUserId = getDriverUserId();
586             DisplayInfo displayInfo = findDisplayForDriverLocked(driverUserId, displayType);
587             if (displayInfo == null) {
588                 return Display.INVALID_DISPLAY;
589             }
590             return displayInfo.display.getDisplayId();
591         }
592     }
593 
594     @GuardedBy("mLock")
595     @Nullable
findDisplayForDriverLocked(@serIdInt int driverUserId, @DisplayTypeEnum int displayType)596     private DisplayInfo findDisplayForDriverLocked(@UserIdInt int driverUserId,
597             @DisplayTypeEnum int displayType) {
598         for (OccupantZoneInfo zoneInfo : getAllOccupantZones()) {
599             if (zoneInfo.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
600                 OccupantConfig config = mActiveOccupantConfigs.get(zoneInfo.zoneId);
601                 if (config == null) {
602                     //No active display for zone, just continue...
603                     continue;
604                 }
605 
606                 if (config.userId == driverUserId) {
607                     for (DisplayInfo displayInfo : config.displayInfos) {
608                         if (displayInfo.displayType == displayType) {
609                             return displayInfo;
610                         }
611                     }
612                 }
613             }
614         }
615         return null;
616     }
617 
618     @Override
getAudioZoneIdForOccupant(int occupantZoneId)619     public int getAudioZoneIdForOccupant(int occupantZoneId) {
620         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
621         synchronized (mLock) {
622             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
623             if (config != null) {
624                 return config.audioZoneId;
625             }
626             // check if the occupant id exist at all
627             if (!mOccupantsConfig.contains(occupantZoneId)) {
628                 return CarAudioManager.INVALID_AUDIO_ZONE;
629             }
630             // Exist but not active
631             return getAudioZoneIdForOccupantLocked(occupantZoneId);
632         }
633     }
634 
635     @GuardedBy("mLock")
getAudioZoneIdForOccupantLocked(int occupantZoneId)636     private int getAudioZoneIdForOccupantLocked(int occupantZoneId) {
637         for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
638             int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
639             if (occupantZoneId == mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId)) {
640                 return audioZoneId;
641             }
642         }
643         return CarAudioManager.INVALID_AUDIO_ZONE;
644     }
645 
646     @Override
getOccupantForAudioZoneId(int audioZoneId)647     public OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) {
648         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
649         synchronized (mLock) {
650             int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId,
651                     OccupantZoneInfo.INVALID_ZONE_ID);
652             if (occupantZoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
653                 return null;
654             }
655             // To support headless zones return the occupant configuration.
656             return mOccupantsConfig.get(occupantZoneId);
657         }
658     }
659 
660     /**
661      * Finds the DisplayConfig for a logical display id.
662      */
663     @Nullable
findDisplayConfigForDisplayId(int displayId)664     public DisplayConfig findDisplayConfigForDisplayId(int displayId) {
665         synchronized (mLock) {
666             return findDisplayConfigForDisplayIdLocked(displayId);
667         }
668     }
669 
670     /**
671      * Finds the DisplayConfig for a physical display port.
672      */
673     @Nullable
findDisplayConfigForPort(int portAddress)674     public DisplayConfig findDisplayConfigForPort(int portAddress) {
675         synchronized (mLock) {
676             return findDisplayConfigForPortLocked(portAddress);
677         }
678     }
679 
680     @GuardedBy("mLock")
681     @Nullable
findDisplayConfigForDisplayIdLocked(int displayId)682     private DisplayConfig findDisplayConfigForDisplayIdLocked(int displayId) {
683         Display display = mDisplayManager.getDisplay(displayId);
684         if (display == null) {
685             return null;
686         }
687         return findDisplayConfigForDisplayLocked(display);
688     }
689 
690     @GuardedBy("mLock")
691     @Nullable
findDisplayConfigForDisplayLocked(Display display)692     private DisplayConfig findDisplayConfigForDisplayLocked(Display display) {
693         int portAddress = DisplayHelper.getPhysicalPort(display);
694         if (portAddress != INVALID_PORT) {
695             DisplayConfig config = mDisplayPortConfigs.get(portAddress);
696             if (config != null) {
697                 return config;
698             }
699         }
700         return mDisplayUniqueIdConfigs.get(DisplayHelper.getUniqueId(display));
701     }
702 
703     @GuardedBy("mLock")
704     @Nullable
findDisplayConfigForPortLocked(int portAddress)705     private DisplayConfig findDisplayConfigForPortLocked(int portAddress) {
706         return portAddress != INVALID_PORT ? mDisplayPortConfigs.get(portAddress) : null;
707     }
708 
709     @Override
getDisplayType(int displayId)710     public int getDisplayType(int displayId) {
711         synchronized (mLock) {
712             DisplayConfig config = findDisplayConfigForDisplayIdLocked(displayId);
713             if (config != null) {
714                 return config.displayType;
715             }
716         }
717         return CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN;
718     }
719 
720     @Override
getUserForOccupant(int occupantZoneId)721     public int getUserForOccupant(int occupantZoneId) {
722         synchronized (mLock) {
723             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
724             if (config == null) {
725                 return CarOccupantZoneManager.INVALID_USER_ID;
726             }
727             return config.userId;
728         }
729     }
730 
731     @Override
getOccupantZoneIdForUserId(@serIdInt int userId)732     public int getOccupantZoneIdForUserId(@UserIdInt int userId) {
733         synchronized (mLock) {
734             for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
735                 OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
736                 if (config.userId == userId) {
737                     return mActiveOccupantConfigs.keyAt(i);
738                 }
739             }
740             Slogf.w(TAG, "Could not find occupantZoneId for userId%d returning invalid "
741                     + "occupant zone id %d", userId, OccupantZoneInfo.INVALID_ZONE_ID);
742             return OccupantZoneInfo.INVALID_ZONE_ID;
743         }
744     }
745 
746     @Override
getOccupantZoneForDisplayId(int displayId)747     public OccupantZoneInfo getOccupantZoneForDisplayId(int displayId) {
748         synchronized (mLock) {
749             DisplayConfig displayConfig = findDisplayConfigForDisplayIdLocked(displayId);
750             if (displayConfig == null) {
751                 Slogf.w(TAG, "getOccupantZoneForDisplayId: Could not find DisplayConfig for "
752                         + "display Id %d", displayId);
753                 return null;
754             }
755 
756             int occupantZoneId = displayConfig.occupantZoneId;
757             if (occupantZoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
758                 Slogf.w(TAG, "getOccupantZoneForDisplayId: Got invalid occupant zone id from "
759                         + "DisplayConfig: %s", displayConfig);
760                 return null;
761             }
762 
763             return mOccupantsConfig.get(occupantZoneId);
764         }
765     }
766 
767     /**
768      * returns the current driver user id.
769      */
getDriverUserId()770     public @UserIdInt int getDriverUserId() {
771         return getCurrentUser();
772     }
773 
774     /**
775      * Sets the mapping for audio zone id to occupant zone id.
776      *
777      * @param audioZoneIdToOccupantZoneMapping map for audio zone id, where key is the audio zone id
778      *                                         and value is the occupant zone id
779      */
setAudioZoneIdsForOccupantZoneIds( @onNull SparseIntArray audioZoneIdToOccupantZoneMapping)780     public void setAudioZoneIdsForOccupantZoneIds(
781             @NonNull SparseIntArray audioZoneIdToOccupantZoneMapping) {
782         Objects.requireNonNull(audioZoneIdToOccupantZoneMapping,
783                 "audioZoneIdToOccupantZoneMapping can not be null");
784         synchronized (mLock) {
785             validateOccupantZoneIdsLocked(audioZoneIdToOccupantZoneMapping);
786             mAudioZoneIdToOccupantZoneIdMapping.clear();
787             for (int index = 0; index < audioZoneIdToOccupantZoneMapping.size(); index++) {
788                 int audioZoneId = audioZoneIdToOccupantZoneMapping.keyAt(index);
789                 mAudioZoneIdToOccupantZoneIdMapping.put(audioZoneId,
790                         audioZoneIdToOccupantZoneMapping.get(audioZoneId));
791             }
792             //If there are any active displays for the zone send change event
793             handleAudioZoneChangesLocked();
794         }
795         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_AUDIO);
796     }
797 
798     @GuardedBy("mLock")
validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping)799     private void validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping) {
800         for (int i = 0; i < audioZoneIdToOccupantZoneMapping.size(); i++) {
801             int occupantZoneId =
802                     audioZoneIdToOccupantZoneMapping.get(audioZoneIdToOccupantZoneMapping.keyAt(i));
803             if (!mOccupantsConfig.contains(occupantZoneId)) {
804                 throw new IllegalArgumentException("occupantZoneId " + occupantZoneId
805                         + " does not exist.");
806             }
807         }
808     }
809 
810     @Override
registerCallback(ICarOccupantZoneCallback callback)811     public void registerCallback(ICarOccupantZoneCallback callback) {
812         mClientCallbacks.register(callback);
813     }
814 
815     @Override
unregisterCallback(ICarOccupantZoneCallback callback)816     public void unregisterCallback(ICarOccupantZoneCallback callback) {
817         mClientCallbacks.unregister(callback);
818     }
819 
820     @Override
assignProfileUserToOccupantZone(int occupantZoneId, @UserIdInt int userId)821     public boolean assignProfileUserToOccupantZone(int occupantZoneId, @UserIdInt int userId) {
822         CarServiceUtils.assertAnyPermission(mContext, android.Manifest.permission.MANAGE_USERS,
823                 Car.PERMISSION_MANAGE_OCCUPANT_ZONE);
824 
825         if (!mEnableProfileUserAssignmentForMultiDisplay) {
826             throw new IllegalStateException("feature not enabled");
827         }
828 
829         UserHandle user = null;
830         synchronized (mLock) {
831             if (occupantZoneId == mDriverZoneId) {
832                 throw new IllegalArgumentException("Driver zone cannot have profile user");
833             }
834             updateEnabledProfilesLocked(getCurrentUser());
835 
836             if (!mProfileUsers.contains(userId)
837                     && userId != CarOccupantZoneManager.INVALID_USER_ID) {
838                 // current user can change while this call is happening, so return false rather
839                 // than throwing exception
840                 Slogf.w(TAG, "Invalid profile user id: %d", userId);
841                 return false;
842             }
843             if (userId != CarOccupantZoneManager.INVALID_USER_ID) {
844                 user = UserHandle.of(userId);
845             }
846         }
847 
848         long token = Binder.clearCallingIdentity();
849         try {
850             if (userId == CarOccupantZoneManager.INVALID_USER_ID) {
851                 return unassignOccupantZoneUnchecked(occupantZoneId)
852                         == CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK;
853             } else {
854                 return assignVisibleUserToOccupantZoneUnchecked(occupantZoneId, user)
855                         == CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK;
856             }
857         } finally {
858             Binder.restoreCallingIdentity(token);
859         }
860     }
861 
862     @Override
assignVisibleUserToOccupantZone(int occupantZoneId, UserHandle user)863     public int assignVisibleUserToOccupantZone(int occupantZoneId, UserHandle user) {
864         CarServiceUtils.assertAnyPermission(mContext, android.Manifest.permission.MANAGE_USERS,
865                 Car.PERMISSION_MANAGE_OCCUPANT_ZONE);
866         Preconditions.checkNotNull(user);
867         long token = Binder.clearCallingIdentity();
868         try {
869             return assignVisibleUserToOccupantZoneUnchecked(occupantZoneId, user);
870         } finally {
871             Binder.restoreCallingIdentity(token);
872         }
873     }
874 
875     /**
876      * Precondition: permission check should be done and binder caller identity should be cleared.
877      */
assignVisibleUserToOccupantZoneUnchecked(int occupantZoneId, @NonNull UserHandle user)878     private int assignVisibleUserToOccupantZoneUnchecked(int occupantZoneId,
879             @NonNull UserHandle user) {
880         int userId;
881         if (user.equals(UserHandle.CURRENT)) {
882             userId = getCurrentUser();
883         } else {
884             userId = user.getIdentifier();
885         }
886 
887         if (!isUserVisible(user)) {
888             Slogf.w(TAG, "Non-visible user %d cannot be allocated to zone %d", userId,
889                     occupantZoneId);
890             return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_FAIL_NON_VISIBLE_USER;
891         }
892 
893         synchronized (mLock) {
894             int userZoneId = getZoneIdForUserIdLocked(userId);
895             if (userZoneId != OccupantZoneInfo.INVALID_ZONE_ID
896                     && mActiveOccupantConfigs.keyAt(userZoneId) != occupantZoneId) {
897                 Slogf.w(TAG, "Cannot assign visible user %d to two different zones simultaneously,"
898                                 + " user is already assigned to %d",
899                         userId, userZoneId);
900                 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_FAIL_ALREADY_ASSIGNED;
901             }
902             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
903             if (config == null) {
904                 Slogf.w(TAG, "Invalid zone:%d", occupantZoneId);
905                 throw new IllegalArgumentException("Invalid occupantZoneId:" + occupantZoneId);
906             }
907             if (config.userId == userId && userId != CarOccupantZoneManager.INVALID_USER_ID) {
908                 Slogf.w(TAG, "assignVisibleUserToOccupantZone zone:%d already set to user:%d",
909                         occupantZoneId, userId);
910                 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK;
911             }
912             if (DBG) Slogf.d(TAG, "Assigned user %d to zone %d", userId, occupantZoneId);
913             config.userId = userId;
914         }
915 
916         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
917         return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK;
918     }
919 
920     @GuardedBy("mLock")
getZoneIdForUserIdLocked(@serIdInt int userId)921     private int getZoneIdForUserIdLocked(@UserIdInt int userId) {
922         for (int i = 0; i < mActiveOccupantConfigs.size(); i++) {
923             OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
924             if (config.userId == userId) {
925                 return mActiveOccupantConfigs.keyAt(i);
926             }
927         }
928         return OccupantZoneInfo.INVALID_ZONE_ID;
929     }
930 
931     @Override
unassignOccupantZone(int occupantZoneId)932     public int unassignOccupantZone(int occupantZoneId) {
933         CarServiceUtils.assertAnyPermission(mContext, android.Manifest.permission.MANAGE_USERS,
934                 Car.PERMISSION_MANAGE_OCCUPANT_ZONE);
935 
936         long token = Binder.clearCallingIdentity();
937         try {
938             return unassignOccupantZoneUnchecked(occupantZoneId);
939         } finally {
940             Binder.restoreCallingIdentity(token);
941         }
942     }
943 
944     /**
945      * Precondition: permission check should be done and binder caller identity should be cleared.
946      */
unassignOccupantZoneUnchecked(int occupantZoneId)947     private int unassignOccupantZoneUnchecked(int occupantZoneId) {
948         synchronized (mLock) {
949             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
950             if (config == null) {
951                 Slogf.w(TAG, "Invalid zone:%d", occupantZoneId);
952                 throw new IllegalArgumentException("Invalid occupantZoneId:" + occupantZoneId);
953             }
954             if (config.userId == CarOccupantZoneManager.INVALID_USER_ID) {
955                 // already unassigned
956                 Slogf.w(TAG, "unassignOccupantZone for already unassigned zone:%d",
957                         occupantZoneId);
958                 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK;
959             }
960             OccupantZoneInfo info = mOccupantsConfig.get(occupantZoneId);
961             if (info.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
962                 Slogf.w(TAG, "Cannot unassign driver zone");
963                 return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_FAIL_DRIVER_ZONE;
964             }
965             if (DBG) Slogf.d(TAG, "Unassigned zone:%d", occupantZoneId);
966             config.userId = CarOccupantZoneManager.INVALID_USER_ID;
967         }
968         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
969 
970         return CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK;
971     }
972 
973     @Override
getMyOccupantZone()974     public OccupantZoneInfo getMyOccupantZone() {
975         int uid = Binder.getCallingUid();
976         // UserHandle.getUserId(uid) can do this in one step but it is hidden API.
977         UserHandle user = UserHandle.getUserHandleForUid(uid);
978         int userId = user.getIdentifier();
979         synchronized (mLock) {
980             for (int i = 0; i < mActiveOccupantConfigs.size(); i++) {
981                 OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
982                 if (config.userId == userId) {
983                     int zoneId = mActiveOccupantConfigs.keyAt(i);
984                     return mOccupantsConfig.get(zoneId);
985                 }
986             }
987         }
988         Slogf.w(TAG, "getMyOccupantZone: No assigned zone for uid:%d", uid);
989         return null;
990     }
991 
992     @Override
getOccupantZoneForUser(UserHandle user)993     public OccupantZoneInfo getOccupantZoneForUser(UserHandle user) {
994         Objects.requireNonNull(user, "User cannot be null");
995         if (user.getIdentifier() == CarOccupantZoneManager.INVALID_USER_ID) {
996             return null;
997         }
998         int occupantZoneId = getOccupantZoneIdForUserId(user.getIdentifier());
999         if (DBG) Slogf.d(TAG, "occupantZoneId that was gotten was %d", occupantZoneId);
1000         synchronized (mLock) {
1001             return mOccupantsConfig.get(occupantZoneId);
1002         }
1003     }
1004 
1005     @Override
getOccupantZone(@ccupantTypeEnum int occupantType, @VehicleAreaSeat.Enum int seat)1006     public OccupantZoneInfo getOccupantZone(@OccupantTypeEnum int occupantType,
1007             @VehicleAreaSeat.Enum int seat) {
1008         synchronized (mLock) {
1009             for (int i = 0; i < mActiveOccupantConfigs.size(); i++) {
1010                 int zoneId = mActiveOccupantConfigs.keyAt(i);
1011                 OccupantZoneInfo info = mOccupantsConfig.get(zoneId);
1012                 if (occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER
1013                         || occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER) {
1014                     if (occupantType == info.occupantType) {
1015                         return info;
1016                     }
1017                 } else {
1018                     if (occupantType == info.occupantType && seat == info.seat) {
1019                         return info;
1020                     }
1021                 }
1022             }
1023             return null;
1024         }
1025     }
1026 
1027     /**
1028      * Gets the occupant zone id for the seat
1029      *
1030      * @param seat The vehicle area seat to be used
1031      *
1032      * @return The occupant zone id for the given seat
1033      */
getOccupantZoneIdForSeat(@ehicleAreaSeat.Enum int seat)1034     public int getOccupantZoneIdForSeat(@VehicleAreaSeat.Enum int seat) {
1035         synchronized (mLock) {
1036             return getOccupantZoneIdForSeatLocked(seat);
1037         }
1038     }
1039 
1040     @GuardedBy("mLock")
getOccupantZoneIdForSeatLocked(@ehicleAreaSeat.Enum int seat)1041     private int getOccupantZoneIdForSeatLocked(@VehicleAreaSeat.Enum int seat) {
1042         for (int i = 0; i < mActiveOccupantConfigs.size(); i++) {
1043             int zoneId = mActiveOccupantConfigs.keyAt(i);
1044             OccupantZoneInfo info = mOccupantsConfig.get(zoneId);
1045             if (seat == info.seat) {
1046                 return zoneId;
1047             }
1048         }
1049         return OccupantZoneInfo.INVALID_ZONE_ID;
1050     }
1051 
1052     @Override
hasDriverZone()1053     public boolean hasDriverZone() {
1054         synchronized (mLock) {
1055             return mDriverZoneId != OccupantZoneInfo.INVALID_ZONE_ID;
1056         }
1057     }
1058 
1059     @Override
hasPassengerZones()1060     public boolean hasPassengerZones() {
1061         synchronized (mLock) {
1062             // There can be only one driver zone. So if there is driver, there should be at least
1063             // two zones to have passenger. If there is no driver zone, having a zone is enough to
1064             // have passenger zone.
1065             boolean hasDriver = mDriverZoneId != OccupantZoneInfo.INVALID_ZONE_ID;
1066             return mActiveOccupantConfigs.size() > (hasDriver ? 1 : 0);
1067         }
1068     }
1069 
1070     @Override
1071     @UserIdInt
getUserForDisplayId(int displayId)1072     public int getUserForDisplayId(int displayId) {
1073         synchronized (mLock) {
1074             for (int i = 0; i < mActiveOccupantConfigs.size(); i++) {
1075                 OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
1076                 for (int j = 0; j < config.displayInfos.size(); j++) {
1077                     if (config.displayInfos.get(j).display.getDisplayId() == displayId) {
1078                         return config.userId;
1079                     }
1080                 }
1081             }
1082         }
1083         Slogf.w(TAG, "Could not find OccupantZone for display Id %d", displayId);
1084         return CarOccupantZoneManager.INVALID_USER_ID;
1085     }
1086 
1087     /** Returns number of passenger zones in the device. */
getNumberOfPassengerZones()1088     public int getNumberOfPassengerZones() {
1089         synchronized (mLock) {
1090             boolean hasDriver = mDriverZoneId != OccupantZoneInfo.INVALID_ZONE_ID;
1091             return mActiveOccupantConfigs.size() - (hasDriver ? 1 : 0);
1092         }
1093     }
1094 
doSyncWithCarServiceHelper(boolean updateDisplay, boolean updateUser, boolean updateConfig)1095     private void doSyncWithCarServiceHelper(boolean updateDisplay, boolean updateUser,
1096             boolean updateConfig) {
1097         int[] passengerDisplays = null;
1098         ArrayMap<Integer, IntArray> allowlists = null;
1099         synchronized (mLock) {
1100             if (updateDisplay) {
1101                 passengerDisplays = getAllActivePassengerDisplaysLocked();
1102             }
1103             if (updateUser) {
1104                 allowlists = createDisplayAllowlistsLocked();
1105             }
1106         }
1107         if (updateDisplay) {
1108             updatePassengerDisplays(passengerDisplays);
1109         }
1110         if (updateUser) {
1111             updateUserAssignmentForDisplays(allowlists);
1112         }
1113         if (updateConfig) {
1114             Resources res = mContext.getResources();
1115             String[] components = res.getStringArray(R.array.config_sourcePreferredComponents);
1116             updateSourcePreferredComponents(components);
1117         }
1118     }
1119 
1120     @GuardedBy("mLock")
getAllActivePassengerDisplaysLocked()1121     private int[] getAllActivePassengerDisplaysLocked() {
1122         IntArray displays = new IntArray();
1123         for (int j = 0; j < mActiveOccupantConfigs.size(); ++j) {
1124             int zoneId = mActiveOccupantConfigs.keyAt(j);
1125             if (zoneId == mDriverZoneId) {
1126                 continue;
1127             }
1128             OccupantConfig config = mActiveOccupantConfigs.valueAt(j);
1129             for (int i = 0; i < config.displayInfos.size(); i++) {
1130                 displays.add(config.displayInfos.get(i).display.getDisplayId());
1131             }
1132         }
1133         return displays.toArray();
1134     }
1135 
updatePassengerDisplays(int[] passengerDisplayIds)1136     private void updatePassengerDisplays(int[] passengerDisplayIds) {
1137         if (passengerDisplayIds == null) {
1138             return;
1139         }
1140         CarServiceHelperWrapper.getInstance().setPassengerDisplays(passengerDisplayIds);
1141     }
1142 
updateSourcePreferredComponents(String[] components)1143     private void updateSourcePreferredComponents(String[] components) {
1144         boolean enableSourcePreferred;
1145         ArrayList<ComponentName> componentNames = null;
1146         if (components == null || components.length == 0) {
1147             enableSourcePreferred = false;
1148             if (DBG) Slogf.d(TAG, "CarLaunchParamsModifier: disable source-preferred");
1149         } else if (components.length == 1 && Objects.equals(components[0], ALL_COMPONENTS)) {
1150             enableSourcePreferred = true;
1151             if (DBG) {
1152                 Slogf.d(TAG, "CarLaunchParamsModifier: enable source-preferred for all Components");
1153             }
1154         } else {
1155             componentNames = new ArrayList<>((components.length));
1156             for (String item : components) {
1157                 ComponentName name = ComponentName.unflattenFromString(item);
1158                 if (name == null) {
1159                     Slogf.e(TAG, "CarLaunchParamsModifier: Wrong ComponentName=" + item);
1160                     return;
1161                 }
1162                 componentNames.add(name);
1163             }
1164             enableSourcePreferred = true;
1165         }
1166         CarServiceHelperWrapper.getInstance().setSourcePreferredComponents(enableSourcePreferred,
1167                 componentNames);
1168         mEnableSourcePreferred = enableSourcePreferred;
1169         mSourcePreferredComponents = componentNames;
1170     }
1171 
1172     @GuardedBy("mLock")
createDisplayAllowlistsLocked()1173     private ArrayMap<Integer, IntArray> createDisplayAllowlistsLocked() {
1174         ArrayMap<Integer, IntArray> allowlists = new ArrayMap<>();
1175         for (int j = 0; j < mActiveOccupantConfigs.size(); ++j) {
1176             int zoneId = mActiveOccupantConfigs.keyAt(j);
1177             if (zoneId == mDriverZoneId) {
1178                 continue;
1179             }
1180             OccupantConfig config = mActiveOccupantConfigs.valueAt(j);
1181             if (config.displayInfos.isEmpty()) {
1182                 continue;
1183             }
1184             // Do not allow any user if it is unassigned.
1185             if (config.userId == CarOccupantZoneManager.INVALID_USER_ID) {
1186                 continue;
1187             }
1188             IntArray displays = allowlists.get(config.userId);
1189             if (displays == null) {
1190                 displays = new IntArray();
1191                 allowlists.put(config.userId, displays);
1192             }
1193             for (int i = 0; i < config.displayInfos.size(); i++) {
1194                 displays.add(config.displayInfos.get(i).display.getDisplayId());
1195             }
1196         }
1197         return allowlists;
1198     }
1199 
updateUserAssignmentForDisplays(ArrayMap<Integer, IntArray> allowlists)1200     private void updateUserAssignmentForDisplays(ArrayMap<Integer, IntArray> allowlists) {
1201         if (allowlists == null || allowlists.isEmpty()) {
1202             return;
1203         }
1204         for (int i = 0; i < allowlists.size(); i++) {
1205             int userId = allowlists.keyAt(i);
1206             CarServiceHelperWrapper.getInstance().setDisplayAllowlistForUser(userId,
1207                     allowlists.valueAt(i).toArray());
1208         }
1209     }
1210 
throwFormatErrorInOccupantZones(String msg)1211     private void throwFormatErrorInOccupantZones(String msg) {
1212         throw new RuntimeException("Format error in config_occupant_zones resource:" + msg);
1213     }
1214 
1215     /** Returns the driver seat. */
getDriverSeat()1216     int getDriverSeat() {
1217         synchronized (mLock) {
1218             return mDriverSeat;
1219         }
1220     }
1221 
1222     @GuardedBy("mLock")
parseOccupantZoneConfigsLocked()1223     private void parseOccupantZoneConfigsLocked() {
1224         final Resources res = mContext.getResources();
1225         // examples:
1226         // <item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item>
1227         // <item>occupantZoneId=1,occupantType=FRONT_PASSENGER,seatRow=1,
1228         // searSide=oppositeDriver</item>
1229         boolean hasDriver = false;
1230         int driverSeat = getDriverSeat();
1231         int driverSeatSide = VehicleAreaSeat.SIDE_LEFT; // default LHD : Left Hand Drive
1232         if (driverSeat == VehicleAreaSeat.SEAT_ROW_1_RIGHT) {
1233             driverSeatSide = VehicleAreaSeat.SIDE_RIGHT;
1234         }
1235         int maxZoneId = OccupantZoneInfo.INVALID_ZONE_ID;
1236         for (String config : res.getStringArray(R.array.config_occupant_zones)) {
1237             int zoneId = OccupantZoneInfo.INVALID_ZONE_ID;
1238             int type = CarOccupantZoneManager.OCCUPANT_TYPE_INVALID;
1239             int seatRow = 0; // invalid row
1240             int seatSide = VehicleAreaSeat.SIDE_LEFT;
1241             String[] entries = config.split(",");
1242             for (String entry : entries) {
1243                 String[] keyValuePair = entry.split("=");
1244                 if (keyValuePair.length != 2) {
1245                     throwFormatErrorInOccupantZones("No key/value pair:" + entry);
1246                 }
1247                 switch (keyValuePair[0]) {
1248                     case "occupantZoneId":
1249                         zoneId = Integer.parseInt(keyValuePair[1]);
1250                         break;
1251                     case "occupantType":
1252                         switch (keyValuePair[1]) {
1253                             case "DRIVER":
1254                                 type = CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER;
1255                                 break;
1256                             case "FRONT_PASSENGER":
1257                                 type = CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER;
1258                                 break;
1259                             case "REAR_PASSENGER":
1260                                 type = CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER;
1261                                 break;
1262                             default:
1263                                 throwFormatErrorInOccupantZones("Unrecognized type:" + entry);
1264                                 break;
1265                         }
1266                         break;
1267                     case "seatRow":
1268                         seatRow = Integer.parseInt(keyValuePair[1]);
1269                         break;
1270                     case "seatSide":
1271                         switch (keyValuePair[1]) {
1272                             case "driver":
1273                                 seatSide = driverSeatSide;
1274                                 break;
1275                             case "oppositeDriver":
1276                                 seatSide = -driverSeatSide;
1277                                 break;
1278                             case "left":
1279                                 seatSide = VehicleAreaSeat.SIDE_LEFT;
1280                                 break;
1281                             case "center":
1282                                 seatSide = VehicleAreaSeat.SIDE_CENTER;
1283                                 break;
1284                             case "right":
1285                                 seatSide = VehicleAreaSeat.SIDE_RIGHT;
1286                                 break;
1287                             default:
1288                                 throwFormatErrorInOccupantZones("Unrecognized seatSide:" + entry);
1289                                 break;
1290                         }
1291                         break;
1292                     default:
1293                         throwFormatErrorInOccupantZones("Unrecognized key:" + entry);
1294                         break;
1295                 }
1296             }
1297             if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
1298                 throwFormatErrorInOccupantZones("Missing zone id:" + config);
1299             }
1300             if (zoneId > maxZoneId) {
1301                 maxZoneId = zoneId;
1302             }
1303             if (type == CarOccupantZoneManager.OCCUPANT_TYPE_INVALID) {
1304                 throwFormatErrorInOccupantZones("Missing type:" + config);
1305             }
1306             if (type == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
1307                 if (hasDriver) {
1308                     throwFormatErrorInOccupantZones("Multiple driver:" + config);
1309                 } else {
1310                     hasDriver = true;
1311                     mDriverZoneId = zoneId;
1312                 }
1313             }
1314             int seat = VehicleAreaSeat.fromRowAndSide(seatRow, seatSide);
1315             if (seat == VehicleAreaSeat.SEAT_UNKNOWN) {
1316                 throwFormatErrorInOccupantZones("Invalid seat:" + config);
1317             }
1318             OccupantZoneInfo info = new OccupantZoneInfo(zoneId, type, seat);
1319             if (mOccupantsConfig.contains(zoneId)) {
1320                 throwFormatErrorInOccupantZones("Duplicate zone id:" + config);
1321             }
1322             mOccupantsConfig.put(zoneId, info);
1323         }
1324         // No zones defined. Then populate driver zone.
1325         if (maxZoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
1326             maxZoneId++;
1327             mDriverZoneId = maxZoneId;
1328             Slogf.w(TAG, "No zones defined, add one as driver:%d", mDriverZoneId);
1329             OccupantZoneInfo info = new OccupantZoneInfo(mDriverZoneId,
1330                     CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER, getDriverSeat());
1331             mOccupantsConfig.put(mDriverZoneId, info);
1332         }
1333     }
1334 
throwFormatErrorInDisplayMapping(String msg)1335     private void throwFormatErrorInDisplayMapping(String msg) {
1336         throw new RuntimeException(
1337                 "Format error in config_occupant_display_mapping resource:" + msg);
1338     }
1339 
1340     @GuardedBy("mLock")
parseDisplayConfigsLocked()1341     private void parseDisplayConfigsLocked() {
1342         final Resources res = mContext.getResources();
1343         final SparseArray<IntArray> inputTypesPerDisplay = new SparseArray<>();
1344         // examples:
1345         // <item>displayPort=0,displayType=MAIN,occupantZoneId=0,inputTypes=DPAD_KEYS|
1346         //            NAVIGATE_KEYS|ROTARY_NAVIGATION</item>
1347         // <item>displayPort=1,displayType=INSTRUMENT_CLUSTER,occupantZoneId=0,
1348         //              inputTypes=DPAD_KEYS</item>
1349         for (String config : res.getStringArray(R.array.config_occupant_display_mapping)) {
1350             int port = INVALID_PORT;
1351             String uniqueId = null;
1352             int type = CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN;
1353             int zoneId = OccupantZoneInfo.INVALID_ZONE_ID;
1354             String[] entries = config.split(",");
1355             for (String entry : entries) {
1356                 String[] keyValuePair = entry.split("=");
1357                 if (keyValuePair.length != 2) {
1358                     throwFormatErrorInDisplayMapping("No key/value pair:" + entry);
1359                 }
1360                 switch (keyValuePair[0]) {
1361                     case "displayPort":
1362                         port = Integer.parseInt(keyValuePair[1]);
1363                         break;
1364                     case "displayUniqueId":
1365                         uniqueId = keyValuePair[1];
1366                         break;
1367                     case "displayType":
1368                         switch (keyValuePair[1]) {
1369                             case "MAIN":
1370                                 type = CarOccupantZoneManager.DISPLAY_TYPE_MAIN;
1371                                 break;
1372                             case "INSTRUMENT_CLUSTER":
1373                                 type = CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER;
1374                                 break;
1375                             case "HUD":
1376                                 type = CarOccupantZoneManager.DISPLAY_TYPE_HUD;
1377                                 break;
1378                             case "INPUT":
1379                                 type = CarOccupantZoneManager.DISPLAY_TYPE_INPUT;
1380                                 break;
1381                             case "AUXILIARY":
1382                                 type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY;
1383                                 break;
1384                             default:
1385                                 throwFormatErrorInDisplayMapping(
1386                                         "Unrecognized display type:" + entry);
1387                                 break;
1388                         }
1389                         inputTypesPerDisplay.set(type, new IntArray());
1390                         break;
1391                     case "occupantZoneId":
1392                         zoneId = Integer.parseInt(keyValuePair[1]);
1393                         break;
1394                     case "inputTypes":
1395                         String[] inputStrings = keyValuePair[1].split("\\|");
1396                         for (int i = 0; i < inputStrings.length; i++) {
1397                             switch (inputStrings[i].trim()) {
1398                                 case "ROTARY_NAVIGATION":
1399                                     inputTypesPerDisplay.get(type).add(
1400                                             CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION);
1401                                     break;
1402                                 case "ROTARY_VOLUME":
1403                                     inputTypesPerDisplay.get(type).add(
1404                                             CarInputManager.INPUT_TYPE_ROTARY_VOLUME);
1405                                     break;
1406                                 case "DPAD_KEYS":
1407                                     inputTypesPerDisplay.get(type).add(
1408                                             CarInputManager.INPUT_TYPE_DPAD_KEYS);
1409                                     break;
1410                                 case "NAVIGATE_KEYS":
1411                                     inputTypesPerDisplay.get(type).add(
1412                                             CarInputManager.INPUT_TYPE_NAVIGATE_KEYS);
1413                                     break;
1414                                 case "SYSTEM_NAVIGATE_KEYS":
1415                                     inputTypesPerDisplay.get(type).add(
1416                                             CarInputManager.INPUT_TYPE_SYSTEM_NAVIGATE_KEYS);
1417                                     break;
1418                                 case "CUSTOM_INPUT_EVENT":
1419                                     inputTypesPerDisplay.get(type).add(
1420                                             CarInputManager.INPUT_TYPE_CUSTOM_INPUT_EVENT);
1421                                     break;
1422                                 case "TOUCH_SCREEN":
1423                                     inputTypesPerDisplay.get(type).add(
1424                                             CarInputManager.INPUT_TYPE_TOUCH_SCREEN);
1425                                     break;
1426                                 case "NONE":
1427                                     inputTypesPerDisplay.get(type).add(
1428                                             CarInputManager.INPUT_TYPE_NONE);
1429                                     break;
1430                                 default:
1431                                     throw new IllegalArgumentException("Invalid input type: "
1432                                             + inputStrings[i]);
1433                             }
1434                         }
1435                         break;
1436                     default:
1437                         throwFormatErrorInDisplayMapping("Unrecognized key:" + entry);
1438                         break;
1439                 }
1440             }
1441 
1442             // Now check validity
1443             checkInputTypeNoneLocked(inputTypesPerDisplay);
1444             if (port == INVALID_PORT && uniqueId == null) {
1445                 throwFormatErrorInDisplayMapping(
1446                         "Missing or invalid displayPort and displayUniqueId:" + config);
1447             }
1448             if (type == CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN) {
1449                 throwFormatErrorInDisplayMapping("Missing or invalid displayType:" + config);
1450             }
1451             if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
1452                 throwFormatErrorInDisplayMapping("Missing or invalid occupantZoneId:" + config);
1453             }
1454             if (!mOccupantsConfig.contains(zoneId)) {
1455                 throwFormatErrorInDisplayMapping(
1456                         "Missing or invalid occupantZoneId:" + config);
1457             }
1458             final DisplayConfig displayConfig = new DisplayConfig(type, zoneId,
1459                     inputTypesPerDisplay.get(type));
1460             if (port != INVALID_PORT) {
1461                 if (mDisplayPortConfigs.contains(port)) {
1462                     throwFormatErrorInDisplayMapping("Duplicate displayPort:" + config);
1463                 }
1464                 mDisplayPortConfigs.put(port, displayConfig);
1465             } else {
1466                 if (mDisplayUniqueIdConfigs.containsKey(uniqueId)) {
1467                     throwFormatErrorInDisplayMapping("Duplicate displayUniqueId:" + config);
1468                 }
1469                 mDisplayUniqueIdConfigs.put(uniqueId, displayConfig);
1470             }
1471         }
1472 
1473         Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
1474         if (findDisplayConfigForDisplayLocked(defaultDisplay) == null) {
1475             int zoneForDefaultDisplay = mDriverZoneId;
1476             if (zoneForDefaultDisplay == OccupantZoneInfo.INVALID_ZONE_ID) {
1477                 // No driver zone but we still need to allocate the default display to the 1st zone,
1478                 // zone id 0.
1479                 zoneForDefaultDisplay = 0;
1480             }
1481             Slogf.w(TAG, "No default display configuration, will assign to zone:"
1482                     + zoneForDefaultDisplay);
1483             mDisplayUniqueIdConfigs.put(DisplayHelper.getUniqueId(defaultDisplay),
1484                     new DisplayConfig(CarOccupantZoneManager.DISPLAY_TYPE_MAIN,
1485                             zoneForDefaultDisplay, inputTypesPerDisplay.get(
1486                                     CarOccupantZoneManager.DISPLAY_TYPE_MAIN)));
1487         }
1488     }
1489 
1490     @GuardedBy("mLock")
checkInputTypeNoneLocked(SparseArray<IntArray> inputTypesPerDisplay)1491     private void checkInputTypeNoneLocked(SparseArray<IntArray> inputTypesPerDisplay) {
1492         for (int i = 0; i < inputTypesPerDisplay.size(); ++i) {
1493             IntArray inputTypes = inputTypesPerDisplay.valueAt(i);
1494             for (int j = 0; j < inputTypes.size(); ++j) {
1495                 if (inputTypes.get(j) == CarInputManager.INPUT_TYPE_NONE && inputTypes.size() > 1) {
1496                     throw new IllegalArgumentException("Display {" + inputTypesPerDisplay.keyAt(i)
1497                             + "} has input type NONE defined along with other input types");
1498                 }
1499             }
1500         }
1501     }
1502 
1503     @GuardedBy("mLock")
addDisplayInfoToOccupantZoneLocked(int zoneId, DisplayInfo info)1504     private void addDisplayInfoToOccupantZoneLocked(int zoneId, DisplayInfo info) {
1505         OccupantConfig occupantConfig = mActiveOccupantConfigs.get(zoneId);
1506         if (occupantConfig == null) {
1507             occupantConfig = new OccupantConfig();
1508             mActiveOccupantConfigs.put(zoneId, occupantConfig);
1509         }
1510         occupantConfig.displayInfos.add(info);
1511     }
1512 
1513     @GuardedBy("mLock")
handleActiveDisplaysLocked()1514     private void handleActiveDisplaysLocked() {
1515         // Clear display info for each zone in preparation for re-populating display info.
1516         for (int i = 0; i < mActiveOccupantConfigs.size(); i++) {
1517             OccupantConfig occupantConfig = mActiveOccupantConfigs.valueAt(i);
1518             occupantConfig.displayInfos.clear();
1519         }
1520 
1521         boolean hasDefaultDisplayConfig = false;
1522         boolean hasDriverZone = hasDriverZone();
1523         for (Display display : mDisplayManager.getDisplays()) {
1524             DisplayConfig displayConfig = findDisplayConfigForDisplayLocked(display);
1525             if (displayConfig == null) {
1526                 Slogf.w(TAG, "Display id:%d does not have configurations",
1527                         display.getDisplayId());
1528                 continue;
1529             }
1530             if (hasDriverZone && display.getDisplayId() == Display.DEFAULT_DISPLAY) {
1531                 if (displayConfig.occupantZoneId != mDriverZoneId) {
1532                     throw new IllegalStateException(
1533                             "Default display should be only assigned to driver zone");
1534                 }
1535                 hasDefaultDisplayConfig = true;
1536             }
1537             addDisplayInfoToOccupantZoneLocked(displayConfig.occupantZoneId,
1538                     new DisplayInfo(display, displayConfig.displayType));
1539         }
1540 
1541         // Remove OccupantConfig in zones without displays.
1542         for (int i = 0; i < mActiveOccupantConfigs.size(); i++) {
1543             OccupantConfig occupantConfig = mActiveOccupantConfigs.valueAt(i);
1544             if (occupantConfig.displayInfos.size() == 0) {
1545                 if (DBG) {
1546                     Slogf.d(TAG, "handleActiveDisplaysLocked: removing zone %d due to no display",
1547                             mActiveOccupantConfigs.keyAt(i));
1548                 }
1549                 mActiveOccupantConfigs.removeAt(i);
1550             }
1551         }
1552 
1553         if (hasDriverZone && !hasDefaultDisplayConfig) {
1554             // This shouldn't happen, since we added the default display config in
1555             // parseDisplayConfigsLocked().
1556             throw new IllegalStateException("Default display not assigned");
1557         }
1558     }
1559 
1560     @VisibleForTesting
getCurrentUser()1561     int getCurrentUser() {
1562         return ActivityManager.getCurrentUser();
1563     }
1564 
1565     @GuardedBy("mLock")
updateEnabledProfilesLocked(@serIdInt int userId)1566     private void updateEnabledProfilesLocked(@UserIdInt int userId) {
1567         mProfileUsers.clear();
1568         List<UserHandle> profileUsers = mUserHandleHelper.getEnabledProfiles(userId);
1569         for (UserHandle profiles : profileUsers) {
1570             if (profiles.getIdentifier() != userId) {
1571                 mProfileUsers.add(profiles.getIdentifier());
1572             }
1573         }
1574     }
1575 
1576     /**
1577      * Checks if the given user is visible. This works in pre-U as well.
1578      */
1579     @VisibleForTesting
1580     @SuppressLint("NewApi")
isUserVisible(@onNull UserHandle user)1581     public boolean isUserVisible(@NonNull UserHandle user) {
1582         if (isPlatformVersionAtLeastU()) {
1583             // createContextAsUser throw exception if user does not exist. So it is not a reliable
1584             // way to query it from car service. We need to catch the exception.
1585             // TODO(b/243864134) Plumb to CarServiceHelper to use UserManagerInternal instead.
1586             try {
1587                 Context userContext = mContext.createContextAsUser(user, /* flags= */ 0);
1588                 UserManager userManager = userContext.getSystemService(UserManager.class);
1589                 return userManager.isUserVisible();
1590             } catch (Exception e) {
1591                 Slogf.w(TAG, "Cannot create User Context for user:" + user.getIdentifier(), e);
1592                 return false;
1593             }
1594         }
1595 
1596         // This is legacy path for T where there is no visible user but we can still support profile
1597         // user as visible as long as it belongs to the current user.
1598         int currentUser = getCurrentUser();
1599         int userId = user.getIdentifier();
1600         if (userId == currentUser) {
1601             return true;
1602         }
1603         synchronized (mLock) {
1604             if (mProfileUsers.contains(userId)) {
1605                 return true;
1606             }
1607         }
1608 
1609         return false;
1610     }
1611 
1612     /** Returns {@code true} if user allocation has changed */
1613     @GuardedBy("mLock")
handleUserChangesLocked()1614     private boolean handleUserChangesLocked() {
1615         int currentUserId = getCurrentUser();
1616 
1617         if (mEnableProfileUserAssignmentForMultiDisplay) {
1618             updateEnabledProfilesLocked(currentUserId);
1619         }
1620 
1621         boolean changed = false;
1622         for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
1623             int zoneId = mActiveOccupantConfigs.keyAt(i);
1624             OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
1625             OccupantZoneInfo info = mOccupantsConfig.get(zoneId);
1626             // Assign the current user to the driver zone if there is a driver zone.
1627             if (info.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER
1628                     && config.userId != currentUserId) {
1629                 config.userId = currentUserId;
1630                 changed = true;
1631                 if (DBG) {
1632                     Slogf.d(TAG, "Changed driver, current user change to %d",
1633                             currentUserId);
1634                 }
1635                 continue;
1636             }
1637             // Do not touch if the zone is un-assigned.
1638             if (config.userId == CarOccupantZoneManager.INVALID_USER_ID) {
1639                 continue;
1640             }
1641             // Now it will be non-driver valid user id.
1642             if (!isUserVisible(UserHandle.of(config.userId))) {
1643                 if (DBG) Slogf.d(TAG, "Unassigned no longer visible user:%d", config.userId);
1644                 config.userId = CarOccupantZoneManager.INVALID_USER_ID;
1645                 changed = true;
1646             }
1647         }
1648 
1649         return changed;
1650     }
1651 
1652     @GuardedBy("mLock")
handleAudioZoneChangesLocked()1653     private void handleAudioZoneChangesLocked() {
1654         for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
1655             int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
1656             int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId);
1657             OccupantConfig occupantConfig = mActiveOccupantConfigs.get(occupantZoneId);
1658             if (occupantConfig == null) {
1659                 //no active display for zone just continue
1660                 continue;
1661             }
1662             // Found an active configuration, add audio to it.
1663             occupantConfig.audioZoneId = audioZoneId;
1664         }
1665     }
1666 
sendConfigChangeEvent(int changeFlags)1667     private void sendConfigChangeEvent(int changeFlags) {
1668         boolean updateDisplay = false;
1669         boolean updateUser = false;
1670         if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0) {
1671             updateDisplay = true;
1672             updateUser = true;
1673         } else if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER) != 0) {
1674             updateUser = true;
1675         }
1676         doSyncWithCarServiceHelper(updateDisplay, updateUser, /* updateConfig= */ false);
1677 
1678         // Schedule remote callback invocation with the handler attached to the same Looper to
1679         // ensure that only one broadcast can be active at one time.
1680         mHandler.post(() -> {
1681             int n = mClientCallbacks.beginBroadcast();
1682             for (int i = 0; i < n; i++) {
1683                 ICarOccupantZoneCallback callback = mClientCallbacks.getBroadcastItem(i);
1684                 try {
1685                     callback.onOccupantZoneConfigChanged(changeFlags);
1686                 } catch (RemoteException ignores) {
1687                     // ignore
1688                 }
1689             }
1690             mClientCallbacks.finishBroadcast();
1691         });
1692     }
1693 
handleUserChange()1694     private void handleUserChange() {
1695         boolean changed;
1696         synchronized (mLock) {
1697             changed = handleUserChangesLocked();
1698         }
1699         if (changed) {
1700             sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
1701         }
1702     }
1703 
handlePassengerStarted()1704     private void handlePassengerStarted() {
1705         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
1706     }
1707 
handlePassengerStopped()1708     private void handlePassengerStopped() {
1709         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
1710     }
1711 
handleDisplayChange()1712     private void handleDisplayChange() {
1713         synchronized (mLock) {
1714             handleActiveDisplaysLocked();
1715             // Audio zones should be re-checked for changed display
1716             handleAudioZoneChangesLocked();
1717             // User should be re-checked for changed displays
1718             handleUserChangesLocked();
1719         }
1720         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY);
1721     }
1722 
enforcePermission(String permissionName)1723     private void enforcePermission(String permissionName) {
1724         if (mContext.checkCallingOrSelfPermission(permissionName)
1725                 != PackageManager.PERMISSION_GRANTED) {
1726             throw new SecurityException("requires permission " + permissionName);
1727         }
1728     }
1729 
1730     /**
1731      * Returns the supported input types for the occupant zone info and display type passed as
1732      * the argument.
1733      *
1734      * @param occupantZoneId the occupant zone id of the supported input types to find
1735      * @param displayType    the display type of the supported input types to find
1736      * @return the supported input types for the occupant zone info and display type passed in as
1737      * the argument
1738      */
getSupportedInputTypes(int occupantZoneId, int displayType)1739     public int[] getSupportedInputTypes(int occupantZoneId, int displayType) {
1740         checkOccupantZone(occupantZoneId, displayType);
1741         synchronized (mLock) {
1742             // Search input type in mDisplayPortConfigs
1743             for (int i = 0; i < mDisplayPortConfigs.size(); i++) {
1744                 DisplayConfig config = mDisplayPortConfigs.valueAt(i);
1745                 if (config.displayType == displayType && config.occupantZoneId == occupantZoneId) {
1746                     return config.inputTypes;
1747                 }
1748             }
1749             // Search input type in mDisplayUniqueIdConfigs
1750             for (int i = 0; i < mDisplayUniqueIdConfigs.size(); i++) {
1751                 DisplayConfig config = mDisplayUniqueIdConfigs.valueAt(i);
1752                 if (config.displayType == displayType && config.occupantZoneId == occupantZoneId) {
1753                     return config.inputTypes;
1754                 }
1755             }
1756         }
1757         return EMPTY_INPUT_SUPPORT_TYPES;
1758     }
1759 
checkOccupantZone(int occupantZoneId, int displayType)1760     private void checkOccupantZone(int occupantZoneId, int displayType) {
1761         if (Display.INVALID_DISPLAY == getDisplayForOccupant(occupantZoneId, displayType)) {
1762             throw new IllegalArgumentException("No display is associated with OccupantZoneInfo "
1763                     + occupantZoneId);
1764         }
1765     }
1766 }
1767