• 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_SWITCHING;
21 
22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
23 import static com.android.car.util.Utils.isEventOfType;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.UserIdInt;
28 import android.app.ActivityManager;
29 import android.car.Car;
30 import android.car.CarInfoManager;
31 import android.car.CarOccupantZoneManager;
32 import android.car.CarOccupantZoneManager.DisplayTypeEnum;
33 import android.car.CarOccupantZoneManager.OccupantTypeEnum;
34 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
35 import android.car.ICarOccupantZone;
36 import android.car.ICarOccupantZoneCallback;
37 import android.car.VehicleAreaSeat;
38 import android.car.builtin.os.UserManagerHelper;
39 import android.car.builtin.util.Slogf;
40 import android.car.builtin.view.DisplayHelper;
41 import android.car.media.CarAudioManager;
42 import android.car.user.CarUserManager.UserLifecycleListener;
43 import android.car.user.UserLifecycleEventFilter;
44 import android.content.ComponentName;
45 import android.content.Context;
46 import android.content.pm.PackageManager;
47 import android.content.res.Resources;
48 import android.hardware.display.DisplayManager;
49 import android.os.Handler;
50 import android.os.Looper;
51 import android.os.RemoteCallbackList;
52 import android.os.RemoteException;
53 import android.os.UserHandle;
54 import android.os.UserManager;
55 import android.util.ArrayMap;
56 import android.util.ArraySet;
57 import android.util.SparseArray;
58 import android.util.SparseIntArray;
59 import android.view.Display;
60 
61 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
62 import com.android.car.internal.ICarServiceHelper;
63 import com.android.car.internal.util.IndentingPrintWriter;
64 import com.android.car.internal.util.IntArray;
65 import com.android.car.user.CarUserService;
66 import com.android.car.user.ExperimentalCarUserService;
67 import com.android.car.user.ExperimentalCarUserService.ZoneUserBindingHelper;
68 import com.android.car.user.UserHandleHelper;
69 import com.android.internal.annotations.GuardedBy;
70 import com.android.internal.annotations.VisibleForTesting;
71 
72 import java.util.ArrayList;
73 import java.util.List;
74 import java.util.Objects;
75 
76 /**
77  * Service to implement CarOccupantZoneManager API.
78  */
79 public final class CarOccupantZoneService extends ICarOccupantZone.Stub
80         implements CarServiceBase {
81 
82     private static final String TAG = CarLog.tagFor(CarOccupantZoneService.class);
83     private static final String ALL_COMPONENTS = "*";
84 
85     private final Object mLock = new Object();
86     private final Context mContext;
87     private final DisplayManager mDisplayManager;
88     private final UserManager mUserManager;
89 
90     private final boolean mEnableProfileUserAssignmentForMultiDisplay;
91 
92     private boolean mEnableSourcePreferred;
93     private ArrayList<ComponentName> mSourcePreferredComponents;
94 
95     /**
96      * Stores android user id of profile users for the current user.
97      */
98     @GuardedBy("mLock")
99     private final ArraySet<Integer> mProfileUsers = new ArraySet<>();
100 
101     /** key: zone id */
102     @GuardedBy("mLock")
103     private final SparseArray<OccupantZoneInfo> mOccupantsConfig = new SparseArray<>();
104 
105     @VisibleForTesting
106     static class DisplayConfig {
107         public final int displayType;
108         public final int occupantZoneId;
109 
DisplayConfig(int displayType, int occupantZoneId)110         DisplayConfig(int displayType, int occupantZoneId) {
111             this.displayType = displayType;
112             this.occupantZoneId = occupantZoneId;
113         }
114 
115         @Override
toString()116         public String toString() {
117             // do not include type as this is only used for dump
118             StringBuilder b = new StringBuilder(64);
119             b.append("{displayType=");
120             b.append(Integer.toHexString(displayType));
121             b.append(" occupantZoneId=");
122             b.append(occupantZoneId);
123             b.append("}");
124             return b.toString();
125         }
126     }
127 
128     /** key: display port address */
129     @GuardedBy("mLock")
130     private final SparseArray<DisplayConfig> mDisplayPortConfigs = new SparseArray<>();
131 
132     /** key: displayUniqueId */
133     @GuardedBy("mLock")
134     private final ArrayMap<String, DisplayConfig> mDisplayUniqueIdConfigs = new ArrayMap<>();
135 
136     /** key: audio zone id */
137     @GuardedBy("mLock")
138     private final SparseIntArray mAudioZoneIdToOccupantZoneIdMapping = new SparseIntArray();
139 
140     @VisibleForTesting
141     static class DisplayInfo {
142         public final Display display;
143         public final int displayType;
144 
DisplayInfo(Display display, int displayType)145         DisplayInfo(Display display, int displayType) {
146             this.display = display;
147             this.displayType = displayType;
148         }
149 
150         @Override
toString()151         public String toString() {
152             // do not include type as this is only used for dump
153             StringBuilder b = new StringBuilder(64);
154             b.append("{displayId=");
155             b.append(display.getDisplayId());
156             b.append(" displayType=");
157             b.append(displayType);
158             b.append("}");
159             return b.toString();
160         }
161     }
162 
163     @VisibleForTesting
164     static class OccupantConfig {
165         public int userId = UserManagerHelper.USER_NULL;
166         public final ArrayList<DisplayInfo> displayInfos = new ArrayList<>();
167         public int audioZoneId = CarAudioManager.INVALID_AUDIO_ZONE;
168 
169         @Override
toString()170         public String toString() {
171             // do not include type as this is only used for dump
172             StringBuilder b = new StringBuilder(128);
173             b.append("{userId=");
174             b.append(userId);
175             b.append(" displays=");
176             for (int i = 0; i < displayInfos.size(); i++) {
177                 b.append(displayInfos.get(i).toString());
178             }
179             b.append(" audioZoneId=");
180             if (audioZoneId != CarAudioManager.INVALID_AUDIO_ZONE) {
181                 b.append(audioZoneId);
182             } else {
183                 b.append("none");
184             }
185             b.append("}");
186             return b.toString();
187         }
188     }
189 
190     /** key : zoneId */
191     @GuardedBy("mLock")
192     private final SparseArray<OccupantConfig> mActiveOccupantConfigs = new SparseArray<>();
193 
194     @GuardedBy("mLock")
195     private ICarServiceHelper mICarServiceHelper;
196 
197     @GuardedBy("mLock")
198     private int mDriverZoneId = OccupantZoneInfo.INVALID_ZONE_ID;
199 
200     @VisibleForTesting
201     final UserLifecycleListener mUserLifecycleListener = event -> {
202         if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) {
203             return;
204         }
205         Slogf.d(TAG, "onEvent(%s)", event);
206 
207         handleUserChange();
208     };
209 
210     final ExperimentalCarUserService.PassengerCallback mPassengerCallback =
211             new ExperimentalCarUserService.PassengerCallback() {
212                 @Override
213                 public void onPassengerStarted(@UserIdInt int passengerId, int zoneId) {
214                     handlePassengerStarted(passengerId, zoneId);
215                 }
216 
217                 @Override
218                 public void onPassengerStopped(@UserIdInt int passengerId) {
219                     handlePassengerStopped(passengerId);
220                 }
221             };
222 
223     @VisibleForTesting
224     final DisplayManager.DisplayListener mDisplayListener =
225             new DisplayManager.DisplayListener() {
226                 @Override
227                 public void onDisplayAdded(int displayId) {
228                     handleDisplayChange();
229                 }
230 
231                 @Override
232                 public void onDisplayRemoved(int displayId) {
233                     handleDisplayChange();
234                 }
235 
236                 @Override
237                 public void onDisplayChanged(int displayId) {
238                     // nothing to do
239                 }
240             };
241 
242     private final RemoteCallbackList<ICarOccupantZoneCallback> mClientCallbacks =
243             new RemoteCallbackList<>();
244 
245     @GuardedBy("mLock")
246     private int mDriverSeat = VehicleAreaSeat.SEAT_UNKNOWN;
247     private final UserHandleHelper mUserHandleHelper;
248 
CarOccupantZoneService(Context context)249     public CarOccupantZoneService(Context context) {
250         this(context, context.getSystemService(DisplayManager.class),
251                 context.getSystemService(UserManager.class),
252                 context.getResources().getBoolean(
253                         R.bool.enableProfileUserAssignmentForMultiDisplay)
254                         && context.getPackageManager().hasSystemFeature(
255                                 PackageManager.FEATURE_MANAGED_USERS),
256                 new UserHandleHelper(context, context.getSystemService(UserManager.class)));
257     }
258 
259     @VisibleForTesting
CarOccupantZoneService(Context context, DisplayManager displayManager, UserManager userManager, boolean enableProfileUserAssignmentForMultiDisplay, UserHandleHelper userHandleHelper)260     public CarOccupantZoneService(Context context, DisplayManager displayManager,
261             UserManager userManager, boolean enableProfileUserAssignmentForMultiDisplay,
262             UserHandleHelper userHandleHelper) {
263         mContext = context;
264         mDisplayManager = displayManager;
265         mUserManager = userManager;
266         mEnableProfileUserAssignmentForMultiDisplay = enableProfileUserAssignmentForMultiDisplay;
267         mUserHandleHelper = userHandleHelper;
268     }
269 
270     @Override
init()271     public void init() {
272         // This does not require connection as binder will be passed directly.
273         Car car = new Car(mContext, /* service= */null, /* handler= */ null);
274         CarInfoManager infoManager = new CarInfoManager(car, CarLocalServices.getService(
275                 CarPropertyService.class));
276         int driverSeat = infoManager.getDriverSeat();
277         synchronized (mLock) {
278             mDriverSeat = driverSeat;
279             parseOccupantZoneConfigsLocked();
280             parseDisplayConfigsLocked();
281             handleActiveDisplaysLocked();
282             handleAudioZoneChangesLocked();
283             handleUserChangesLocked();
284         }
285         CarUserService userService = CarLocalServices.getService(CarUserService.class);
286         UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder()
287                 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build();
288         userService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener);
289         ExperimentalCarUserService experimentalUserService =
290                 CarLocalServices.getService(ExperimentalCarUserService.class);
291         if (experimentalUserService != null) {
292             experimentalUserService.addPassengerCallback(mPassengerCallback);
293         }
294         mDisplayManager.registerDisplayListener(mDisplayListener,
295                 new Handler(Looper.getMainLooper()));
296         ZoneUserBindingHelper helper = new ZoneUserBindingHelper() {
297             @Override
298             @NonNull
299             public List<OccupantZoneInfo> getOccupantZones(@OccupantTypeEnum int occupantType) {
300                 List<OccupantZoneInfo> zones = new ArrayList<OccupantZoneInfo>();
301                 for (OccupantZoneInfo ozi : getAllOccupantZones()) {
302                     if (ozi.occupantType == occupantType) {
303                         zones.add(ozi);
304                     }
305                 }
306                 return zones;
307             }
308 
309             @Override
310             public boolean assignUserToOccupantZone(@UserIdInt int userId, int zoneId) {
311                 // Check if the user is already assigned to the other zone.
312                 synchronized (mLock) {
313                     for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
314                         OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
315                         if (config.userId == userId && zoneId != mActiveOccupantConfigs.keyAt(i)) {
316                             Slogf.w(TAG, "cannot assign user to two different zone simultaneously");
317                             return false;
318                         }
319                     }
320                     OccupantConfig zoneConfig = mActiveOccupantConfigs.get(zoneId);
321                     if (zoneConfig == null) {
322                         Slogf.w(TAG, "cannot find the zone(%d)", zoneId);
323                         return false;
324                     }
325                     if (zoneConfig.userId != UserManagerHelper.USER_NULL
326                             && zoneConfig.userId != userId) {
327                         Slogf.w(TAG, "other user already occupies the zone(%d)", zoneId);
328                         return false;
329                     }
330                     zoneConfig.userId = userId;
331                     return true;
332                 }
333             }
334 
335             @Override
336             public boolean unassignUserFromOccupantZone(@UserIdInt int userId) {
337                 synchronized (mLock) {
338                     for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
339                         OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
340                         if (config.userId == userId) {
341                             config.userId = UserManagerHelper.USER_NULL;
342                             break;
343                         }
344                     }
345                     return true;
346                 }
347             }
348 
349             @Override
350             public boolean isPassengerDisplayAvailable() {
351                 for (OccupantZoneInfo ozi : getAllOccupantZones()) {
352                     if (getDisplayForOccupant(ozi.zoneId,
353                             CarOccupantZoneManager.DISPLAY_TYPE_MAIN) != Display.INVALID_DISPLAY
354                             && ozi.occupantType != CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
355                         return true;
356                     }
357                 }
358                 return false;
359             }
360         };
361         if (experimentalUserService != null) {
362             experimentalUserService.setZoneUserBindingHelper(helper);
363         }
364     }
365 
366     @Override
release()367     public void release() {
368         mDisplayManager.unregisterDisplayListener(mDisplayListener);
369         CarUserService userService = CarLocalServices.getService(CarUserService.class);
370         userService.removeUserLifecycleListener(mUserLifecycleListener);
371         ExperimentalCarUserService experimentalUserService =
372                 CarLocalServices.getService(ExperimentalCarUserService.class);
373         if (experimentalUserService != null) {
374             experimentalUserService.removePassengerCallback(mPassengerCallback);
375         }
376         synchronized (mLock) {
377             mOccupantsConfig.clear();
378             mDisplayPortConfigs.clear();
379             mDisplayUniqueIdConfigs.clear();
380             mAudioZoneIdToOccupantZoneIdMapping.clear();
381             mActiveOccupantConfigs.clear();
382         }
383     }
384 
385     /** Return cloned mOccupantsConfig for testing */
386     @VisibleForTesting
387     @NonNull
getOccupantsConfig()388     public SparseArray<OccupantZoneInfo> getOccupantsConfig() {
389         synchronized (mLock) {
390             return mOccupantsConfig.clone();
391         }
392     }
393 
394     /** Return cloned mDisplayPortConfigs for testing */
395     @VisibleForTesting
396     @NonNull
getDisplayPortConfigs()397     public SparseArray<DisplayConfig> getDisplayPortConfigs() {
398         synchronized (mLock) {
399             return mDisplayPortConfigs.clone();
400         }
401     }
402 
403     /** Return cloned mDisplayUniqueIdConfigs for testing */
404     @VisibleForTesting
405     @NonNull
getDisplayUniqueIdConfigs()406     ArrayMap<String, DisplayConfig> getDisplayUniqueIdConfigs() {
407         synchronized (mLock) {
408             return new ArrayMap<>(mDisplayUniqueIdConfigs);
409         }
410     }
411 
412     /** Return cloned mAudioConfigs for testing */
413     @VisibleForTesting
414     @NonNull
getAudioConfigs()415     SparseIntArray getAudioConfigs() {
416         synchronized (mLock) {
417             return mAudioZoneIdToOccupantZoneIdMapping.clone();
418         }
419     }
420 
421     /** Return cloned mActiveOccupantConfigs for testing */
422     @VisibleForTesting
423     @NonNull
getActiveOccupantConfigs()424     public SparseArray<OccupantConfig> getActiveOccupantConfigs() {
425         synchronized (mLock) {
426             return mActiveOccupantConfigs.clone();
427         }
428     }
429 
430     @Override
431     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)432     public void dump(IndentingPrintWriter writer) {
433         writer.println("*OccupantZoneService*");
434         synchronized (mLock) {
435             writer.println("**mOccupantsConfig**");
436             for (int i = 0; i < mOccupantsConfig.size(); ++i) {
437                 writer.println(" zoneId=" + mOccupantsConfig.keyAt(i)
438                         + " info=" + mOccupantsConfig.valueAt(i));
439             }
440             writer.println("**mDisplayConfigs**");
441             for (int i = 0; i < mDisplayPortConfigs.size(); ++i) {
442                 writer.println(" port=" + mDisplayPortConfigs.keyAt(i)
443                         + " config=" + mDisplayPortConfigs.valueAt(i));
444             }
445             for (int i = 0; i < mDisplayUniqueIdConfigs.size(); ++i) {
446                 writer.println(" uniqueId=" + mDisplayUniqueIdConfigs.keyAt(i)
447                         + " config=" + mDisplayUniqueIdConfigs.valueAt(i));
448             }
449             writer.println("**mAudioZoneIdToOccupantZoneIdMapping**");
450             for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
451                 int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
452                 writer.println(" audioZoneId=" + Integer.toHexString(audioZoneId)
453                         + " zoneId=" + mAudioZoneIdToOccupantZoneIdMapping.valueAt(index));
454             }
455             writer.println("**mActiveOccupantConfigs**");
456             for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
457                 writer.println(" zoneId=" + mActiveOccupantConfigs.keyAt(i)
458                         + " config=" + mActiveOccupantConfigs.valueAt(i));
459             }
460             writer.println("mEnableProfileUserAssignmentForMultiDisplay:"
461                     + mEnableProfileUserAssignmentForMultiDisplay);
462             writer.println("mEnableSourcePreferred:"
463                     + mEnableSourcePreferred);
464             writer.append("mSourcePreferredComponents: [");
465             if (mSourcePreferredComponents != null) {
466                 for (int i = 0; i < mSourcePreferredComponents.size(); ++i) {
467                     if (i > 0) writer.append(' ');
468                     writer.append(mSourcePreferredComponents.get(i).toString());
469                 }
470             }
471             writer.println(']');
472         }
473     }
474 
475     @Override
getAllOccupantZones()476     public List<OccupantZoneInfo> getAllOccupantZones() {
477         synchronized (mLock) {
478             List<OccupantZoneInfo> infos = new ArrayList<>();
479             for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
480                 int zoneId = mActiveOccupantConfigs.keyAt(i);
481                 // no need for deep copy as OccupantZoneInfo itself is static.
482                 infos.add(mOccupantsConfig.get(zoneId));
483             }
484             return infos;
485         }
486     }
487 
488     @Override
getAllDisplaysForOccupantZone(int occupantZoneId)489     public int[] getAllDisplaysForOccupantZone(int occupantZoneId) {
490         synchronized (mLock) {
491             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
492             if (config == null) {
493                 return new int[0];
494             }
495             int[] displayIds = new int[config.displayInfos.size()];
496             for (int i = 0; i < config.displayInfos.size(); i++) {
497                 displayIds[i] = config.displayInfos.get(i).display.getDisplayId();
498             }
499             return displayIds;
500         }
501     }
502 
503     @Override
getDisplayForOccupant(int occupantZoneId, int displayType)504     public int getDisplayForOccupant(int occupantZoneId, int displayType) {
505         synchronized (mLock) {
506             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
507             if (config == null) {
508                 return Display.INVALID_DISPLAY;
509             }
510             for (int i = 0; i < config.displayInfos.size(); i++) {
511                 if (displayType == config.displayInfos.get(i).displayType) {
512                     return config.displayInfos.get(i).display.getDisplayId();
513                 }
514             }
515         }
516         return Display.INVALID_DISPLAY;
517     }
518 
getAllDisplayIdsForDriver(int displayType)519     public IntArray getAllDisplayIdsForDriver(int displayType) {
520         synchronized (mLock) {
521             OccupantConfig config = mActiveOccupantConfigs.get(mDriverZoneId);
522             if (config == null) {
523                 return new IntArray(0);
524             }
525             IntArray displayIds = new IntArray(config.displayInfos.size());
526             Slogf.d(TAG, "getAllDisplayIdsForDriver: displayInfos=" + config.displayInfos);
527             for (int i = 0; i < config.displayInfos.size(); i++) {
528                 DisplayInfo displayInfo = config.displayInfos.get(i);
529                 if (displayInfo.displayType == displayType) {
530                     displayIds.add(displayInfo.display.getDisplayId());
531                 }
532             }
533             return displayIds;
534         }
535     }
536 
537     @Override
getDisplayIdForDriver(@isplayTypeEnum int displayType)538     public int getDisplayIdForDriver(@DisplayTypeEnum int displayType) {
539         enforcePermission(Car.ACCESS_PRIVATE_DISPLAY_ID);
540         synchronized (mLock) {
541             int driverUserId = getDriverUserId();
542             DisplayInfo displayInfo = findDisplayForDriverLocked(driverUserId, displayType);
543             if (displayInfo == null) {
544                 return Display.INVALID_DISPLAY;
545             }
546             return displayInfo.display.getDisplayId();
547         }
548     }
549 
550     @GuardedBy("mLock")
551     @Nullable
findDisplayForDriverLocked(int driverUserId, @DisplayTypeEnum int displayType)552     private DisplayInfo findDisplayForDriverLocked(int driverUserId,
553             @DisplayTypeEnum int displayType) {
554         for (OccupantZoneInfo zoneInfo : getAllOccupantZones()) {
555             if (zoneInfo.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
556                 OccupantConfig config = mActiveOccupantConfigs.get(zoneInfo.zoneId);
557                 if (config == null) {
558                     //No active display for zone, just continue...
559                     continue;
560                 }
561 
562                 if (config.userId == driverUserId) {
563                     for (DisplayInfo displayInfo : config.displayInfos) {
564                         if (displayInfo.displayType == displayType) {
565                             return displayInfo;
566                         }
567                     }
568                 }
569             }
570         }
571         return null;
572     }
573 
574     @Override
getAudioZoneIdForOccupant(int occupantZoneId)575     public int getAudioZoneIdForOccupant(int occupantZoneId) {
576         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
577         synchronized (mLock) {
578             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
579             if (config != null) {
580                 return config.audioZoneId;
581             }
582             // check if the occupant id exist at all
583             if (!mOccupantsConfig.contains(occupantZoneId)) {
584                 return CarAudioManager.INVALID_AUDIO_ZONE;
585             }
586             // Exist but not active
587             return getAudioZoneIdForOccupantLocked(occupantZoneId);
588         }
589     }
590 
591     @GuardedBy("mLock")
getAudioZoneIdForOccupantLocked(int occupantZoneId)592     private int getAudioZoneIdForOccupantLocked(int occupantZoneId) {
593         for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
594             int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
595             if (occupantZoneId == mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId)) {
596                 return audioZoneId;
597             }
598         }
599         return CarAudioManager.INVALID_AUDIO_ZONE;
600     }
601 
602     @Override
getOccupantForAudioZoneId(int audioZoneId)603     public CarOccupantZoneManager.OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) {
604         enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
605         synchronized (mLock) {
606             int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId,
607                     OccupantZoneInfo.INVALID_ZONE_ID);
608             if (occupantZoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
609                 return null;
610             }
611             // To support headless zones return the occupant configuration.
612             return mOccupantsConfig.get(occupantZoneId);
613         }
614     }
615 
616     @GuardedBy("mLock")
617     @Nullable
findDisplayConfigForDisplayIdLocked(int displayId)618     private DisplayConfig findDisplayConfigForDisplayIdLocked(int displayId) {
619         Display display = mDisplayManager.getDisplay(displayId);
620         if (display == null) {
621             return null;
622         }
623         return findDisplayConfigForDisplayLocked(display);
624     }
625 
626     @GuardedBy("mLock")
627     @Nullable
findDisplayConfigForDisplayLocked(Display display)628     private DisplayConfig findDisplayConfigForDisplayLocked(Display display) {
629         int portAddress = DisplayHelper.getPhysicalPort(display);
630         if (portAddress != INVALID_PORT) {
631             DisplayConfig config = mDisplayPortConfigs.get(portAddress);
632             if (config != null) {
633                 return config;
634             }
635         }
636         return mDisplayUniqueIdConfigs.get(DisplayHelper.getUniqueId(display));
637     }
638 
639     @Override
getDisplayType(int displayId)640     public int getDisplayType(int displayId) {
641         synchronized (mLock) {
642             DisplayConfig config = findDisplayConfigForDisplayIdLocked(displayId);
643             if (config != null) {
644                 return config.displayType;
645             }
646         }
647         return CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN;
648     }
649 
650     @Override
getUserForOccupant(int occupantZoneId)651     public int getUserForOccupant(int occupantZoneId) {
652         synchronized (mLock) {
653             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
654             if (config == null) {
655                 return UserManagerHelper.USER_NULL;
656             }
657             return config.userId;
658         }
659     }
660 
661     @Override
getOccupantZoneIdForUserId(int userId)662     public int getOccupantZoneIdForUserId(int userId) {
663         synchronized (mLock) {
664             for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
665                 OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
666                 if (config.userId == userId) {
667                     return mActiveOccupantConfigs.keyAt(i);
668                 }
669             }
670             Slogf.w(TAG, "Could not find occupantZoneId for userId%d returning invalid "
671                     + "occupant zone id %d", userId, OccupantZoneInfo.INVALID_ZONE_ID);
672             return OccupantZoneInfo.INVALID_ZONE_ID;
673         }
674     }
675 
676     /**
677      * returns the current driver user id.
678      */
getDriverUserId()679     public @UserIdInt int getDriverUserId() {
680         return getCurrentUser();
681     }
682 
683     /**
684      * Sets the mapping for audio zone id to occupant zone id.
685      *
686      * @param audioZoneIdToOccupantZoneMapping map for audio zone id, where key is the audio zone id
687      * and value is the occupant zone id.
688      */
setAudioZoneIdsForOccupantZoneIds( @onNull SparseIntArray audioZoneIdToOccupantZoneMapping)689     public void setAudioZoneIdsForOccupantZoneIds(
690             @NonNull SparseIntArray audioZoneIdToOccupantZoneMapping) {
691         Objects.requireNonNull(audioZoneIdToOccupantZoneMapping,
692                 "audioZoneIdToOccupantZoneMapping can not be null");
693         synchronized (mLock) {
694             validateOccupantZoneIdsLocked(audioZoneIdToOccupantZoneMapping);
695             mAudioZoneIdToOccupantZoneIdMapping.clear();
696             for (int index = 0; index < audioZoneIdToOccupantZoneMapping.size(); index++) {
697                 int audioZoneId = audioZoneIdToOccupantZoneMapping.keyAt(index);
698                 mAudioZoneIdToOccupantZoneIdMapping.put(audioZoneId,
699                         audioZoneIdToOccupantZoneMapping.get(audioZoneId));
700             }
701             //If there are any active displays for the zone send change event
702             handleAudioZoneChangesLocked();
703         }
704         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_AUDIO);
705     }
706 
707     @GuardedBy("mLock")
validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping)708     private void validateOccupantZoneIdsLocked(SparseIntArray audioZoneIdToOccupantZoneMapping) {
709         for (int i = 0; i < audioZoneIdToOccupantZoneMapping.size(); i++) {
710             int occupantZoneId =
711                     audioZoneIdToOccupantZoneMapping.get(audioZoneIdToOccupantZoneMapping.keyAt(i));
712             if (!mOccupantsConfig.contains(occupantZoneId)) {
713                 throw new IllegalArgumentException("occupantZoneId " + occupantZoneId
714                         + " does not exist.");
715             }
716         }
717     }
718 
719     @Override
registerCallback(ICarOccupantZoneCallback callback)720     public void registerCallback(ICarOccupantZoneCallback callback) {
721         mClientCallbacks.register(callback);
722     }
723 
724     @Override
unregisterCallback(ICarOccupantZoneCallback callback)725     public void unregisterCallback(ICarOccupantZoneCallback callback) {
726         mClientCallbacks.unregister(callback);
727     }
728 
729     @Override
assignProfileUserToOccupantZone(int occupantZoneId, int userId)730     public boolean assignProfileUserToOccupantZone(int occupantZoneId, int userId) {
731         enforcePermission(android.Manifest.permission.MANAGE_USERS);
732         if (!mEnableProfileUserAssignmentForMultiDisplay) {
733             throw new IllegalStateException("feature not enabled");
734         }
735         int currentUser = getCurrentUser();
736 
737         synchronized (mLock) {
738             if (occupantZoneId == mDriverZoneId) {
739                 throw new IllegalArgumentException("Driver zone cannot have profile user");
740             }
741             updateEnabledProfilesLocked(currentUser);
742 
743             if (!mProfileUsers.contains(userId) && userId != UserManagerHelper.USER_NULL) {
744                 // current user can change while this call is happening, so return false rather
745                 // than throwing exception
746                 Slogf.w(TAG, "Invalid profile user id: %d", userId);
747                 return false;
748             }
749             if (!mUserManager.isUserRunning(UserHandle.of(userId))) {
750                 Slogf.w(TAG, "User%d is not running.", userId);
751                 return false;
752             }
753             OccupantConfig config = mActiveOccupantConfigs.get(occupantZoneId);
754             if (config == null) {
755                 throw new IllegalArgumentException("Invalid occupantZoneId:" + occupantZoneId);
756             }
757             if (config.userId == userId && userId != UserManagerHelper.USER_NULL) {
758                 Slogf.w(TAG, "assignProfileUserToOccupantZone zone:%d already set to user:%",
759                         occupantZoneId, userId);
760                 return true;
761             }
762             if (userId == UserManagerHelper.USER_NULL) {
763                 config.userId = currentUser;
764             } else {
765                 config.userId = userId;
766             }
767         }
768         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
769         return true;
770     }
771 
772     /**
773      * Sets {@code ICarServiceHelper}.
774      */
setCarServiceHelper(ICarServiceHelper helper)775     public void setCarServiceHelper(ICarServiceHelper helper) {
776         doSyncWithCarServiceHelper(helper, /* updateDisplay= */ true, /* updateUser= */ true,
777                 /* updateConfig= */ true);
778     }
779 
doSyncWithCarServiceHelper(@ullable ICarServiceHelper helper, boolean updateDisplay, boolean updateUser, boolean updateConfig)780     private void doSyncWithCarServiceHelper(@Nullable ICarServiceHelper helper,
781             boolean updateDisplay, boolean updateUser, boolean updateConfig) {
782         int[] passengerDisplays = null;
783         ArrayMap<Integer, IntArray> allowlists = null;
784         ICarServiceHelper helperToUse = helper;
785         synchronized (mLock) {
786             if (helper == null) {
787                 if (mICarServiceHelper == null) { // helper not set yet.
788                     return;
789                 }
790                 helperToUse = mICarServiceHelper;
791             } else {
792                 mICarServiceHelper = helper;
793             }
794             if (updateDisplay) {
795                 passengerDisplays = getAllActivePassengerDisplaysLocked();
796             }
797             if (updateUser) {
798                 allowlists = createDisplayAllowlistsLocked();
799             }
800         }
801         if (updateDisplay) {
802             updatePassengerDisplays(helperToUse, passengerDisplays);
803         }
804         if (updateUser) {
805             updateUserAssignmentForDisplays(helperToUse, allowlists);
806         }
807         if (updateConfig) {
808             Resources res = mContext.getResources();
809             String[] components = res.getStringArray(R.array.config_sourcePreferredComponents);
810             updateSourcePreferredComponents(helperToUse, components);
811         }
812     }
813 
814     @GuardedBy("mLock")
getAllActivePassengerDisplaysLocked()815     private int[] getAllActivePassengerDisplaysLocked() {
816         IntArray displays = new IntArray();
817         for (int j = 0; j < mActiveOccupantConfigs.size(); ++j) {
818             int zoneId = mActiveOccupantConfigs.keyAt(j);
819             if (zoneId == mDriverZoneId) {
820                 continue;
821             }
822             OccupantConfig config = mActiveOccupantConfigs.valueAt(j);
823             for (int i = 0; i < config.displayInfos.size(); i++) {
824                 displays.add(config.displayInfos.get(i).display.getDisplayId());
825             }
826         }
827         return displays.toArray();
828     }
829 
updatePassengerDisplays(ICarServiceHelper helper, int[] passengerDisplayIds)830     private void updatePassengerDisplays(ICarServiceHelper helper, int[] passengerDisplayIds) {
831         if (passengerDisplayIds == null) {
832             return;
833         }
834         try {
835             helper.setPassengerDisplays(passengerDisplayIds);
836         } catch (RemoteException e) {
837             Slogf.e(TAG, "ICarServiceHelper.setPassengerDisplays failed", e);
838         }
839     }
840 
updateSourcePreferredComponents(ICarServiceHelper helper, String[] components)841     private void updateSourcePreferredComponents(ICarServiceHelper helper, String[] components) {
842         boolean enableSourcePreferred;
843         ArrayList<ComponentName> componentNames = null;
844         if (components == null || components.length == 0) {
845             enableSourcePreferred = false;
846             Slogf.i(TAG, "CarLaunchParamsModifier: disable source-preferred");
847         } else if (components.length == 1 && components[0].equals(ALL_COMPONENTS)) {
848             enableSourcePreferred = true;
849             Slogf.i(TAG, "CarLaunchParamsModifier: enable source-preferred for all Components");
850         } else {
851             componentNames = new ArrayList<>((components.length));
852             for (String item : components) {
853                 ComponentName name = ComponentName.unflattenFromString(item);
854                 if (name == null) {
855                     Slogf.e(TAG, "CarLaunchParamsModifier: Wrong ComponentName=" + item);
856                     return;
857                 }
858                 componentNames.add(name);
859             }
860             enableSourcePreferred = true;
861         }
862         try {
863             helper.setSourcePreferredComponents(enableSourcePreferred, componentNames);
864             mEnableSourcePreferred = enableSourcePreferred;
865             mSourcePreferredComponents = componentNames;
866         } catch (RemoteException e) {
867             Slogf.e(TAG, "ICarServiceHelper.setSourcePreferredComponents failed");
868         }
869     }
870 
871     @GuardedBy("mLock")
createDisplayAllowlistsLocked()872     private ArrayMap<Integer, IntArray> createDisplayAllowlistsLocked() {
873         ArrayMap<Integer, IntArray> allowlists = new ArrayMap<>();
874         for (int j = 0; j < mActiveOccupantConfigs.size(); ++j) {
875             int zoneId = mActiveOccupantConfigs.keyAt(j);
876             if (zoneId == mDriverZoneId) {
877                 continue;
878             }
879             OccupantConfig config = mActiveOccupantConfigs.valueAt(j);
880             if (config.displayInfos.isEmpty()) {
881                 continue;
882             }
883             // user like driver can have multiple zones assigned, so add them all.
884             IntArray displays = allowlists.get(config.userId);
885             if (displays == null) {
886                 displays = new IntArray();
887                 allowlists.put(config.userId, displays);
888             }
889             for (int i = 0; i < config.displayInfos.size(); i++) {
890                 displays.add(config.displayInfos.get(i).display.getDisplayId());
891             }
892         }
893         return allowlists;
894     }
895 
updateUserAssignmentForDisplays(ICarServiceHelper helper, ArrayMap<Integer, IntArray> allowlists)896     private void updateUserAssignmentForDisplays(ICarServiceHelper helper,
897             ArrayMap<Integer, IntArray> allowlists) {
898         if (allowlists == null || allowlists.isEmpty()) {
899             return;
900         }
901         try {
902             for (int i = 0; i < allowlists.size(); i++) {
903                 int userId = allowlists.keyAt(i);
904                 helper.setDisplayAllowlistForUser(userId, allowlists.valueAt(i).toArray());
905             }
906         } catch (RemoteException e) {
907             Slogf.e(TAG, "ICarServiceHelper.setDisplayAllowlistForUser failed", e);
908         }
909     }
910 
throwFormatErrorInOccupantZones(String msg)911     private void throwFormatErrorInOccupantZones(String msg) {
912         throw new RuntimeException("Format error in config_occupant_zones resource:" + msg);
913     }
914 
915     // For overriding in test
916     @VisibleForTesting
getDriverSeat()917     int getDriverSeat() {
918         synchronized (mLock) {
919             return mDriverSeat;
920         }
921     }
922 
923     @GuardedBy("mLock")
parseOccupantZoneConfigsLocked()924     private void parseOccupantZoneConfigsLocked() {
925         final Resources res = mContext.getResources();
926         // examples:
927         // <item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item>
928         // <item>occupantZoneId=1,occupantType=FRONT_PASSENGER,seatRow=1,
929         // searSide=oppositeDriver</item>
930         boolean hasDriver = false;
931         int driverSeat = getDriverSeat();
932         int driverSeatSide = VehicleAreaSeat.SIDE_LEFT; // default LHD : Left Hand Drive
933         if (driverSeat == VehicleAreaSeat.SEAT_ROW_1_RIGHT) {
934             driverSeatSide = VehicleAreaSeat.SIDE_RIGHT;
935         }
936         int maxZoneId = OccupantZoneInfo.INVALID_ZONE_ID;
937         for (String config : res.getStringArray(R.array.config_occupant_zones)) {
938             int zoneId = OccupantZoneInfo.INVALID_ZONE_ID;
939             int type = CarOccupantZoneManager.OCCUPANT_TYPE_INVALID;
940             int seatRow = 0; // invalid row
941             int seatSide = VehicleAreaSeat.SIDE_LEFT;
942             String[] entries = config.split(",");
943             for (String entry : entries) {
944                 String[] keyValuePair = entry.split("=");
945                 if (keyValuePair.length != 2) {
946                     throwFormatErrorInOccupantZones("No key/value pair:" + entry);
947                 }
948                 switch (keyValuePair[0]) {
949                     case "occupantZoneId":
950                         zoneId = Integer.parseInt(keyValuePair[1]);
951                         break;
952                     case "occupantType":
953                         switch (keyValuePair[1]) {
954                             case "DRIVER":
955                                 type = CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER;
956                                 break;
957                             case "FRONT_PASSENGER":
958                                 type = CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER;
959                                 break;
960                             case "REAR_PASSENGER":
961                                 type = CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER;
962                                 break;
963                             default:
964                                 throwFormatErrorInOccupantZones("Unrecognized type:" + entry);
965                                 break;
966                         }
967                         break;
968                     case "seatRow":
969                         seatRow = Integer.parseInt(keyValuePair[1]);
970                         break;
971                     case "seatSide":
972                         switch (keyValuePair[1]) {
973                             case "driver":
974                                 seatSide = driverSeatSide;
975                                 break;
976                             case "oppositeDriver":
977                                 seatSide = -driverSeatSide;
978                                 break;
979                             case "left":
980                                 seatSide = VehicleAreaSeat.SIDE_LEFT;
981                                 break;
982                             case "center":
983                                 seatSide = VehicleAreaSeat.SIDE_CENTER;
984                                 break;
985                             case "right":
986                                 seatSide = VehicleAreaSeat.SIDE_RIGHT;
987                                 break;
988                             default:
989                                 throwFormatErrorInOccupantZones("Unregognized seatSide:" + entry);
990                                 break;
991 
992                         }
993                         break;
994                     default:
995                         throwFormatErrorInOccupantZones("Unrecognized key:" + entry);
996                         break;
997                 }
998             }
999             if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
1000                 throwFormatErrorInOccupantZones("Missing zone id:" + config);
1001             }
1002             if (zoneId > maxZoneId) {
1003                 maxZoneId = zoneId;
1004             }
1005             if (type == CarOccupantZoneManager.OCCUPANT_TYPE_INVALID) {
1006                 throwFormatErrorInOccupantZones("Missing type:" + config);
1007             }
1008             if (type == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
1009                 if (hasDriver) {
1010                     throwFormatErrorInOccupantZones("Multiple driver:" + config);
1011                 } else {
1012                     hasDriver = true;
1013                     mDriverZoneId = zoneId;
1014                 }
1015             }
1016             int seat = VehicleAreaSeat.fromRowAndSide(seatRow, seatSide);
1017             if (seat == VehicleAreaSeat.SEAT_UNKNOWN) {
1018                 throwFormatErrorInOccupantZones("Invalid seat:" + config);
1019             }
1020             OccupantZoneInfo info = new OccupantZoneInfo(zoneId, type, seat);
1021             if (mOccupantsConfig.contains(zoneId)) {
1022                 throwFormatErrorInOccupantZones("Duplicate zone id:" + config);
1023             }
1024             mOccupantsConfig.put(zoneId, info);
1025         }
1026         if (!hasDriver) {
1027             maxZoneId++;
1028             mDriverZoneId = maxZoneId;
1029             Slogf.w(TAG, "No driver zone, add one:%d", mDriverZoneId);
1030             OccupantZoneInfo info = new OccupantZoneInfo(mDriverZoneId,
1031                     CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER, getDriverSeat());
1032             mOccupantsConfig.put(mDriverZoneId, info);
1033         }
1034     }
1035 
throwFormatErrorInDisplayMapping(String msg)1036     private void throwFormatErrorInDisplayMapping(String msg) {
1037         throw new RuntimeException(
1038                 "Format error in config_occupant_display_mapping resource:" + msg);
1039     }
1040 
1041     @GuardedBy("mLock")
parseDisplayConfigsLocked()1042     private void parseDisplayConfigsLocked() {
1043         final Resources res = mContext.getResources();
1044         // examples:
1045         // <item>displayPort=0,displayType=MAIN,occupantZoneId=0</item>
1046         // <item>displayPort=1,displayType=INSTRUMENT_CLUSTER,occupantZoneId=0</item>
1047         for (String config : res.getStringArray(R.array.config_occupant_display_mapping)) {
1048             int port = INVALID_PORT;
1049             String uniqueId = null;
1050             int type = CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN;
1051             int zoneId = OccupantZoneInfo.INVALID_ZONE_ID;
1052             String[] entries = config.split(",");
1053             for (String entry : entries) {
1054                 String[] keyValuePair = entry.split("=");
1055                 if (keyValuePair.length != 2) {
1056                     throwFormatErrorInDisplayMapping("No key/value pair:" + entry);
1057                 }
1058                 switch (keyValuePair[0]) {
1059                     case "displayPort":
1060                         port = Integer.parseInt(keyValuePair[1]);
1061                         break;
1062                     case "displayUniqueId":
1063                         uniqueId = keyValuePair[1];
1064                         break;
1065                     case "displayType":
1066                         switch (keyValuePair[1]) {
1067                             case "MAIN":
1068                                 type = CarOccupantZoneManager.DISPLAY_TYPE_MAIN;
1069                                 break;
1070                             case "INSTRUMENT_CLUSTER":
1071                                 type = CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER;
1072                                 break;
1073                             case "HUD":
1074                                 type = CarOccupantZoneManager.DISPLAY_TYPE_HUD;
1075                                 break;
1076                             case "INPUT":
1077                                 type = CarOccupantZoneManager.DISPLAY_TYPE_INPUT;
1078                                 break;
1079                             case "AUXILIARY":
1080                                 type = CarOccupantZoneManager.DISPLAY_TYPE_AUXILIARY;
1081                                 break;
1082                             default:
1083                                 throwFormatErrorInDisplayMapping(
1084                                         "Unrecognized display type:" + entry);
1085                                 break;
1086                         }
1087                         break;
1088                     case "occupantZoneId":
1089                         zoneId = Integer.parseInt(keyValuePair[1]);
1090                         break;
1091                     default:
1092                         throwFormatErrorInDisplayMapping("Unrecognized key:" + entry);
1093                         break;
1094 
1095                 }
1096             }
1097             // Now check validity
1098             if (port == INVALID_PORT && uniqueId == null) {
1099                 throwFormatErrorInDisplayMapping(
1100                         "Missing or invalid displayPort and displayUniqueId:" + config);
1101             }
1102 
1103             if (type == CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN) {
1104                 throwFormatErrorInDisplayMapping("Missing or invalid displayType:" + config);
1105             }
1106             if (zoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
1107                 throwFormatErrorInDisplayMapping("Missing or invalid occupantZoneId:" + config);
1108             }
1109             if (!mOccupantsConfig.contains(zoneId)) {
1110                 throwFormatErrorInDisplayMapping(
1111                         "Missing or invalid occupantZoneId:" + config);
1112             }
1113             DisplayConfig displayConfig = new DisplayConfig(type, zoneId);
1114             if (port != INVALID_PORT) {
1115                 if (mDisplayPortConfigs.contains(port)) {
1116                     throwFormatErrorInDisplayMapping("Duplicate displayPort:" + config);
1117                 }
1118                 mDisplayPortConfigs.put(port, displayConfig);
1119             } else {
1120                 if (mDisplayUniqueIdConfigs.containsKey(uniqueId)) {
1121                     throwFormatErrorInDisplayMapping("Duplicate displayUniqueId:" + config);
1122                 }
1123                 mDisplayUniqueIdConfigs.put(uniqueId, displayConfig);
1124             }
1125         }
1126         Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
1127         if (findDisplayConfigForDisplayLocked(defaultDisplay) == null) {
1128             Slogf.w(TAG, "No default display configuration, will assign to driver zone");
1129             mDisplayUniqueIdConfigs.put(DisplayHelper.getUniqueId(defaultDisplay),
1130                     new DisplayConfig(CarOccupantZoneManager.DISPLAY_TYPE_MAIN, mDriverZoneId));
1131         }
1132     }
1133 
1134     @GuardedBy("mLock")
addDisplayInfoToOccupantZoneLocked(int zoneId, DisplayInfo info)1135     private void addDisplayInfoToOccupantZoneLocked(int zoneId, DisplayInfo info) {
1136         OccupantConfig occupantConfig = mActiveOccupantConfigs.get(zoneId);
1137         if (occupantConfig == null) {
1138             occupantConfig = new OccupantConfig();
1139             mActiveOccupantConfigs.put(zoneId, occupantConfig);
1140         }
1141         occupantConfig.displayInfos.add(info);
1142     }
1143 
1144     @GuardedBy("mLock")
handleActiveDisplaysLocked()1145     private void handleActiveDisplaysLocked() {
1146         mActiveOccupantConfigs.clear();
1147         boolean hasDefaultDisplayConfig = false;
1148         for (Display display : mDisplayManager.getDisplays()) {
1149             DisplayConfig displayConfig = findDisplayConfigForDisplayLocked(display);
1150             if (displayConfig == null) {
1151                 Slogf.w(TAG, "Display id:%d does not have configurations",
1152                         display.getDisplayId());
1153                 continue;
1154             }
1155             if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
1156                 if (displayConfig.occupantZoneId != mDriverZoneId) {
1157                     throw new IllegalStateException(
1158                             "Default display should be only assigned to driver zone");
1159                 }
1160                 hasDefaultDisplayConfig = true;
1161             }
1162             addDisplayInfoToOccupantZoneLocked(displayConfig.occupantZoneId,
1163                     new DisplayInfo(display, displayConfig.displayType));
1164         }
1165         if (!hasDefaultDisplayConfig) {
1166             // This shouldn't happen, since we added the default display config in
1167             // parseDisplayConfigsLocked().
1168             throw new IllegalStateException("Default display not assigned");
1169         }
1170     }
1171 
1172     @VisibleForTesting
getCurrentUser()1173     int getCurrentUser() {
1174         return ActivityManager.getCurrentUser();
1175     }
1176 
1177     @GuardedBy("mLock")
updateEnabledProfilesLocked(int userId)1178     private void updateEnabledProfilesLocked(int userId) {
1179         mProfileUsers.clear();
1180         List<UserHandle> profileUsers = mUserHandleHelper.getEnabledProfiles(userId);
1181         for (UserHandle profiles : profileUsers) {
1182             if (profiles.getIdentifier() != userId) {
1183                 mProfileUsers.add(profiles.getIdentifier());
1184             }
1185         }
1186     }
1187 
1188     @GuardedBy("mLock")
handleUserChangesLocked()1189     private void handleUserChangesLocked() {
1190         int driverUserId = getCurrentUser();
1191 
1192         if (mEnableProfileUserAssignmentForMultiDisplay) {
1193             updateEnabledProfilesLocked(driverUserId);
1194         }
1195 
1196         for (int i = 0; i < mActiveOccupantConfigs.size(); ++i) {
1197             int zoneId = mActiveOccupantConfigs.keyAt(i);
1198             OccupantConfig config = mActiveOccupantConfigs.valueAt(i);
1199             // mProfileUsers empty if not supported
1200             if (mProfileUsers.contains(config.userId)) {
1201                 Slogf.i(TAG, "Profile user:%d already assigned for occupant zone:%d",
1202                         config.userId, zoneId);
1203             } else {
1204                 config.userId = driverUserId;
1205             }
1206         }
1207     }
1208 
1209     @GuardedBy("mLock")
handleAudioZoneChangesLocked()1210     private void handleAudioZoneChangesLocked() {
1211         for (int index = 0; index < mAudioZoneIdToOccupantZoneIdMapping.size(); index++) {
1212             int audioZoneId = mAudioZoneIdToOccupantZoneIdMapping.keyAt(index);
1213             int occupantZoneId = mAudioZoneIdToOccupantZoneIdMapping.get(audioZoneId);
1214             OccupantConfig occupantConfig = mActiveOccupantConfigs.get(occupantZoneId);
1215             if (occupantConfig == null) {
1216                 //no active display for zone just continue
1217                 continue;
1218             }
1219             // Found an active configuration, add audio to it.
1220             occupantConfig.audioZoneId = audioZoneId;
1221         }
1222     }
1223 
sendConfigChangeEvent(int changeFlags)1224     private void sendConfigChangeEvent(int changeFlags) {
1225         boolean updateDisplay = false;
1226         boolean updateUser = false;
1227         if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0) {
1228             updateDisplay = true;
1229             updateUser = true;
1230         } else if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER) != 0) {
1231             updateUser = true;
1232         }
1233         doSyncWithCarServiceHelper(/* helper= */ null, updateDisplay, updateUser,
1234                 /* updateConfig= */ false);
1235 
1236         final int n = mClientCallbacks.beginBroadcast();
1237         for (int i = 0; i < n; i++) {
1238             ICarOccupantZoneCallback callback = mClientCallbacks.getBroadcastItem(i);
1239             try {
1240                 callback.onOccupantZoneConfigChanged(changeFlags);
1241             } catch (RemoteException ignores) {
1242                 // ignore
1243             }
1244         }
1245         mClientCallbacks.finishBroadcast();
1246     }
1247 
handleUserChange()1248     private void handleUserChange() {
1249         synchronized (mLock) {
1250             handleUserChangesLocked();
1251         }
1252         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
1253     }
1254 
handlePassengerStarted(@serIdInt int passengerId, int zoneId)1255     private void handlePassengerStarted(@UserIdInt int passengerId, int zoneId) {
1256         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
1257     }
1258 
handlePassengerStopped(@serIdInt int passengerId)1259     private void handlePassengerStopped(@UserIdInt int passengerId) {
1260         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
1261     }
1262 
handleDisplayChange()1263     private void handleDisplayChange() {
1264         synchronized (mLock) {
1265             handleActiveDisplaysLocked();
1266             //audio zones should be re-checked for changed display
1267             handleAudioZoneChangesLocked();
1268             // user should be re-checked for changed displays
1269             handleUserChangesLocked();
1270         }
1271         sendConfigChangeEvent(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY);
1272     }
1273 
enforcePermission(String permissionName)1274     private void enforcePermission(String permissionName) {
1275         if (mContext.checkCallingOrSelfPermission(permissionName)
1276                 != PackageManager.PERMISSION_GRANTED) {
1277             throw new SecurityException("requires permission " + permissionName);
1278         }
1279     }
1280 }
1281