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