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