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