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 android.car;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SystemApi;
26 import android.annotation.UserIdInt;
27 import android.hardware.display.DisplayManager;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.os.RemoteException;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.util.Slog;
38 import android.view.Display;
39 
40 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
41 import com.android.car.internal.util.Lists;
42 
43 import java.lang.annotation.ElementType;
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.lang.annotation.Target;
47 import java.lang.ref.WeakReference;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.concurrent.CopyOnWriteArrayList;
52 
53 /**
54  * API to get information on displays and users in the car.
55  *
56  * <p>From car version {@link CarVersion.VERSION_CODES#UPSIDE_DOWN_CAKE_0}, system without the
57  * driver zone is allowed and the current user will not be the driver.
58  */
59 public class CarOccupantZoneManager extends CarManagerBase {
60 
61     private static final String TAG = CarOccupantZoneManager.class.getSimpleName();
62 
63     /**
64      * Display type is not known. In some system, some displays may be just public display without
65      * any additional information and such displays will be treated as unknown.
66      */
67     public static final int DISPLAY_TYPE_UNKNOWN = 0;
68 
69     /**
70      * Main display users are interacting with. UI for the user will be launched to this display by
71      * default. {@link Display#DEFAULT_DISPLAY} will be always have this type. But there can be
72      * multiple of this type as each passenger can have their own main display.
73      */
74     public static final int DISPLAY_TYPE_MAIN = 1;
75 
76     /** Instrument cluster display. This may exist only for driver. */
77     public static final int DISPLAY_TYPE_INSTRUMENT_CLUSTER = 2;
78 
79     /** Head Up Display. This may exist only for driver. */
80     public static final int DISPLAY_TYPE_HUD = 3;
81 
82     /** Dedicated display for showing IME for {@link #DISPLAY_TYPE_MAIN} */
83     public static final int DISPLAY_TYPE_INPUT = 4;
84 
85     /**
86      * Auxiliary display which can provide additional screen for {@link #DISPLAY_TYPE_MAIN}.
87      * Activity running in {@link #DISPLAY_TYPE_MAIN} may use {@link android.app.Presentation} to
88      * show additional information.
89      */
90     public static final int DISPLAY_TYPE_AUXILIARY = 5;
91 
92     /**
93      * Auxiliary display which can provide additional screen for {@link #DISPLAY_TYPE_MAIN}.
94      * Activities running in {@link #DISPLAY_TYPE_MAIN} or other auxiliary displays may be moved
95      * to this type of display.
96      * @hide
97      */
98     public static final int DISPLAY_TYPE_AUXILIARY_2 = 6;
99 
100     /**
101      * Auxiliary display which can provide additional screen for {@link #DISPLAY_TYPE_MAIN}.
102      * Activities running in {@link #DISPLAY_TYPE_MAIN} or other auxiliary displays may be moved
103      * to this type of display.
104      * @hide
105      */
106     public static final int DISPLAY_TYPE_AUXILIARY_3 = 7;
107 
108     /**
109      * Auxiliary display which can provide additional screen for {@link #DISPLAY_TYPE_MAIN}.
110      * Activities running in {@link #DISPLAY_TYPE_MAIN} or other auxiliary displays may be moved
111      * to this type of display.
112      * @hide
113      */
114     public static final int DISPLAY_TYPE_AUXILIARY_4 = 8;
115 
116     /**
117      * Auxiliary display which can provide additional screen for {@link #DISPLAY_TYPE_MAIN}.
118      * Activities running in {@link #DISPLAY_TYPE_MAIN} or other auxiliary displays may be moved
119      * to this type of display.
120      * @hide
121      */
122     public static final int DISPLAY_TYPE_AUXILIARY_5 = 9;
123 
124     /**
125      * Display specifically used for showing display compatibility apps.
126      * @hide
127      */
128     public static final int DISPLAY_TYPE_DISPLAY_COMPATIBILITY = 10;
129 
130     /** @hide */
131     @Retention(RetentionPolicy.SOURCE)
132     @IntDef(prefix = "DISPLAY_TYPE_", value = {
133             DISPLAY_TYPE_UNKNOWN,
134             DISPLAY_TYPE_MAIN,
135             DISPLAY_TYPE_INSTRUMENT_CLUSTER,
136             DISPLAY_TYPE_HUD,
137             DISPLAY_TYPE_INPUT,
138             DISPLAY_TYPE_AUXILIARY,
139             DISPLAY_TYPE_AUXILIARY_2,
140             DISPLAY_TYPE_AUXILIARY_3,
141             DISPLAY_TYPE_AUXILIARY_4,
142             DISPLAY_TYPE_AUXILIARY_5,
143             DISPLAY_TYPE_DISPLAY_COMPATIBILITY,
144     })
145     @Target({ElementType.TYPE_USE})
146     public @interface DisplayTypeEnum {}
147 
148     /** @hide */
149     public static final int OCCUPANT_TYPE_INVALID = -1;
150 
151     /**
152      * Represents the driver. There can be one or zero driver for the system. Zero driver situation
153      * can happen if the system is configured to support only passengers.
154      */
155     public static final int OCCUPANT_TYPE_DRIVER = 0;
156 
157     /**
158      * Represents front passengers who sit in front side of car. Most cars will have only
159      * one passenger of this type but this can be multiple.
160      */
161     public static final int OCCUPANT_TYPE_FRONT_PASSENGER = 1;
162 
163     /** Represents passengers in rear seats. There can be multiple passengers of this type. */
164     public static final int OCCUPANT_TYPE_REAR_PASSENGER = 2;
165 
166     /** @hide */
167     @Retention(RetentionPolicy.SOURCE)
168     @IntDef(prefix = "OCCUPANT_TYPE_", value = {
169             OCCUPANT_TYPE_DRIVER,
170             OCCUPANT_TYPE_FRONT_PASSENGER,
171             OCCUPANT_TYPE_REAR_PASSENGER,
172     })
173     @Target({ElementType.TYPE_USE})
174     public @interface OccupantTypeEnum {}
175 
176     /**
177      * Represents an occupant zone in a car.
178      *
179      * <p>Each occupant does not necessarily represent single person but it is for mapping to one
180      * set of displays. For example, for display located in center rear seat, both left and right
181      * side passengers may use it but it is abstracted as a single occupant zone.</p>
182      */
183     public static final class OccupantZoneInfo implements Parcelable {
184         /** @hide */
185         public static final int INVALID_ZONE_ID = -1;
186 
187         /**
188          * This is an unique id to distinguish each occupant zone.
189          *
190          * <p>This can be helpful to distinguish different zones when {@link #occupantType} and
191          * {@link #seat} are the same for multiple occupant / passenger zones.</p>
192          *
193          * <p>This id will remain the same for the same zone across configuration changes like
194          * user switching or display changes</p>
195          */
196         public int zoneId;
197         /** Represents type of passenger */
198         @OccupantTypeEnum
199         public final int occupantType;
200         /**
201          * Represents seat assigned for the occupant. In some system, this can have value of
202          * {@link VehicleAreaSeat#SEAT_UNKNOWN}.
203          */
204         @VehicleAreaSeat.Enum
205         public final int seat;
206 
207         /** @hide */
OccupantZoneInfo(int zoneId, @OccupantTypeEnum int occupantType, @VehicleAreaSeat.Enum int seat)208         public OccupantZoneInfo(int zoneId, @OccupantTypeEnum int occupantType,
209                 @VehicleAreaSeat.Enum int seat) {
210             this.zoneId = zoneId;
211             this.occupantType = occupantType;
212             this.seat = seat;
213         }
214 
215         /** @hide */
OccupantZoneInfo(Parcel in)216         public OccupantZoneInfo(Parcel in) {
217             zoneId = in.readInt();
218             occupantType = in.readInt();
219             seat = in.readInt();
220         }
221 
222         @Override
223         @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
describeContents()224         public int describeContents() {
225             return 0;
226         }
227 
228         @Override
writeToParcel(Parcel dest, int flags)229         public void writeToParcel(Parcel dest, int flags) {
230             dest.writeInt(zoneId);
231             dest.writeInt(occupantType);
232             dest.writeInt(seat);
233         }
234 
235         @Override
equals(Object other)236         public boolean equals(Object other) {
237             if (this == other) {
238                 return true;
239             }
240             if (!(other instanceof OccupantZoneInfo)) {
241                 return false;
242             }
243             OccupantZoneInfo that = (OccupantZoneInfo) other;
244             return zoneId == that.zoneId && occupantType == that.occupantType
245                     && seat == that.seat;
246         }
247 
248         @Override
hashCode()249         public int hashCode() {
250             int hash = 23;
251             hash = hash * 17 + zoneId;
252             hash = hash * 17 + occupantType;
253             hash = hash * 17 + seat;
254             return hash;
255         }
256 
257         public static final Parcelable.Creator<OccupantZoneInfo> CREATOR =
258                 new Parcelable.Creator<>() {
259                     public OccupantZoneInfo createFromParcel(Parcel in) {
260                         return new OccupantZoneInfo(in);
261                     }
262 
263                     public OccupantZoneInfo[] newArray(int size) {
264                         return new OccupantZoneInfo[size];
265                     }
266                 };
267 
268         @Override
toString()269         public String toString() {
270             StringBuilder b = new StringBuilder(64);
271             b.append("OccupantZoneInfo{zoneId=");
272             b.append(zoneId);
273             b.append(" type=");
274             b.append(occupantType);
275             b.append(" seat=");
276             b.append(Integer.toHexString(seat));
277             b.append("}");
278             return b.toString();
279         }
280     }
281 
282     /**
283      * Zone config change caused by display changes. A display could have been added / removed.
284      * Besides change in display itself. this can lead into removal / addition of passenger zones.
285      */
286     public static final int ZONE_CONFIG_CHANGE_FLAG_DISPLAY = 0x1;
287 
288     /** Zone config change caused by user change. Assigned user for passenger zones have changed. */
289     public static final int ZONE_CONFIG_CHANGE_FLAG_USER = 0x2;
290 
291     /**
292      * Zone config change caused by audio zone change.
293      * Assigned audio zone for passenger zones have changed.
294      **/
295     public static final int ZONE_CONFIG_CHANGE_FLAG_AUDIO = 0x4;
296 
297     /** @hide */
298     @IntDef(flag = true, prefix = {"ZONE_CONFIG_CHANGE_FLAG_"}, value = {
299             ZONE_CONFIG_CHANGE_FLAG_DISPLAY,
300             ZONE_CONFIG_CHANGE_FLAG_USER,
301             ZONE_CONFIG_CHANGE_FLAG_AUDIO,
302     })
303     @Retention(RetentionPolicy.SOURCE)
304     @interface ZoneConfigChangeFlags {}
305 
306     /**
307      * The assignment was successful.
308      *
309      * @hide
310      */
311     @SystemApi
312     public static final int USER_ASSIGNMENT_RESULT_OK = 0;
313 
314     /**
315      * The operation has failed as the user is already assigned to other zone. If the goal is to
316      * move the user, the current zone should be unassigned first.
317      *
318      * @hide
319      */
320     @SystemApi
321     public static final int USER_ASSIGNMENT_RESULT_FAIL_ALREADY_ASSIGNED = 1;
322 
323     /**
324      * The assigned user is not a {@link UserManager#isUserVisible() visible user}.
325      *
326      * @hide
327      */
328     @SystemApi
329     public static final int USER_ASSIGNMENT_RESULT_FAIL_NON_VISIBLE_USER = 2;
330 
331     /**
332      * Assigning non-current user to driver zone or un-assigning driver zone will fail with this
333      * error.
334      *
335      * @hide
336      */
337     @SystemApi
338     public static final int USER_ASSIGNMENT_RESULT_FAIL_DRIVER_ZONE = 3;
339 
340     /** @hide */
341     @IntDef(flag = false, prefix = {"USER_ASSIGNMENT_RESULT_"}, value = {
342             USER_ASSIGNMENT_RESULT_OK,
343             USER_ASSIGNMENT_RESULT_FAIL_ALREADY_ASSIGNED,
344             USER_ASSIGNMENT_RESULT_FAIL_NON_VISIBLE_USER,
345             USER_ASSIGNMENT_RESULT_FAIL_DRIVER_ZONE,
346     })
347     @Retention(RetentionPolicy.SOURCE)
348     @interface UserAssignmentResult {}
349 
350     /**
351      * Invalid user ID. Zone with this user ID has no allocated user. Should have the same value
352      * with {@link UserHandle#USER_NULL}.
353      */
354     public static final @UserIdInt int INVALID_USER_ID = -10000;
355 
356     /**
357      * Listener to monitor any Occupant Zone configuration change. The configuration change can
358      * involve some displays removed or new displays added. Also it can happen when assigned user
359      * for any zone changes.
360      */
361     public interface OccupantZoneConfigChangeListener {
362 
363         /**
364          * Configuration for occupant zones has changed. Apps should re-check all
365          * occupant zone configs. This can be caused by events like user switching and
366          * display addition / removal.
367          *
368          * @param changeFlags Reason for the zone change.
369          */
onOccupantZoneConfigChanged(@oneConfigChangeFlags int changeFlags)370         void onOccupantZoneConfigChanged(@ZoneConfigChangeFlags int changeFlags);
371     }
372 
373     private final DisplayManager mDisplayManager;
374     private final EventHandler mEventHandler;
375 
376     private final ICarOccupantZone mService;
377 
378     private final ICarOccupantZoneCallbackImpl mBinderCallback;
379 
380     private final CopyOnWriteArrayList<OccupantZoneConfigChangeListener> mListeners =
381             new CopyOnWriteArrayList<>();
382 
383     /**
384      * Gets an instance of the CarOccupantZoneManager.
385      *
386      * <p>Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
387      *
388      * @hide
389      */
CarOccupantZoneManager(Car car, IBinder service)390     public CarOccupantZoneManager(Car car, IBinder service) {
391         super(car);
392         mService = ICarOccupantZone.Stub.asInterface(service);
393         mBinderCallback = new ICarOccupantZoneCallbackImpl(this);
394         mDisplayManager = getContext().getSystemService(DisplayManager.class);
395         mEventHandler = new EventHandler(getEventHandler().getLooper());
396     }
397 
398     /**
399      * Returns all available occupants in the system. If no occupant zone is defined in the system
400      * or none is available at the moment, it will return empty list.
401      */
402     @NonNull
getAllOccupantZones()403     public List<OccupantZoneInfo> getAllOccupantZones() {
404         try {
405             return mService.getAllOccupantZones();
406         } catch (RemoteException e) {
407             return handleRemoteExceptionFromCarService(e, Collections.emptyList());
408         }
409     }
410 
411     /**
412      * Returns all displays assigned for the given occupant zone. If no display is available for
413      * the passenger, it will return empty list.
414      */
415     @NonNull
getAllDisplaysForOccupant(@onNull OccupantZoneInfo occupantZone)416     public List<Display> getAllDisplaysForOccupant(@NonNull OccupantZoneInfo occupantZone) {
417         assertNonNullOccupant(occupantZone);
418         try {
419             int[] displayIds = mService.getAllDisplaysForOccupantZone(occupantZone.zoneId);
420             ArrayList<Display> displays = new ArrayList<>(displayIds.length);
421             for (int i = 0; i < displayIds.length; i++) {
422                 // quick confidence check while getDisplay can still handle invalid display
423                 if (displayIds[i] == Display.INVALID_DISPLAY) {
424                     continue;
425                 }
426                 Display display = mDisplayManager.getDisplay(displayIds[i]);
427                 if (display != null) { // necessary as display list could have changed.
428                     displays.add(display);
429                 }
430             }
431             return displays;
432         } catch (RemoteException e) {
433             return handleRemoteExceptionFromCarService(e, Collections.emptyList());
434         }
435     }
436 
437     /**
438      * Gets the display for the occupant for the specified display type, or returns {@code null}
439      * if no display matches the requirements.
440      *
441      * @param displayType This should be a valid display type and passing
442      *                    {@link #DISPLAY_TYPE_UNKNOWN} will always lead into {@code null} return.
443      */
444     @Nullable
getDisplayForOccupant(@onNull OccupantZoneInfo occupantZone, @DisplayTypeEnum int displayType)445     public Display getDisplayForOccupant(@NonNull OccupantZoneInfo occupantZone,
446             @DisplayTypeEnum int displayType) {
447         assertNonNullOccupant(occupantZone);
448         try {
449             int displayId = mService.getDisplayForOccupant(occupantZone.zoneId, displayType);
450             // quick confidence check while getDisplay can still handle invalid display
451             if (displayId == Display.INVALID_DISPLAY) {
452                 return null;
453             }
454             return mDisplayManager.getDisplay(displayId);
455         } catch (RemoteException e) {
456             return handleRemoteExceptionFromCarService(e, null);
457         }
458     }
459 
460     /**
461      * Returns the display id for the driver.
462      *
463      * <p>This method just returns the display id for the requested type. The returned display id
464      * may correspond to a private display and the client may not have access to it.
465      *
466      * @param displayType the display type
467      * @return the driver's display id or {@link Display#INVALID_DISPLAY} when no such display
468      * exists or if the driver zone does not exist.
469      *
470      * @hide
471      */
472     @SystemApi
473     @RequiresPermission(Car.ACCESS_PRIVATE_DISPLAY_ID)
getDisplayIdForDriver(@isplayTypeEnum int displayType)474     public int getDisplayIdForDriver(@DisplayTypeEnum int displayType) {
475         try {
476             return mService.getDisplayIdForDriver(displayType);
477         } catch (RemoteException e) {
478             return handleRemoteExceptionFromCarService(e, Display.INVALID_DISPLAY);
479         }
480     }
481 
482     /**
483      * Gets the audio zone id for the occupant, or returns
484      * {@code CarAudioManager.INVALID_AUDIO_ZONE} if no audio zone matches the requirements.
485      * throws InvalidArgumentException if occupantZone does not exist.
486      *
487      * @hide
488      */
489     @SystemApi
490     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getAudioZoneIdForOccupant(@onNull OccupantZoneInfo occupantZone)491     public int getAudioZoneIdForOccupant(@NonNull OccupantZoneInfo occupantZone) {
492         assertNonNullOccupant(occupantZone);
493         try {
494             return mService.getAudioZoneIdForOccupant(occupantZone.zoneId);
495         } catch (RemoteException e) {
496             return handleRemoteExceptionFromCarService(e, null);
497         }
498     }
499 
500     /**
501      * Gets occupant for the audio zone id, or returns {@code null}
502      * if no audio zone matches the requirements.
503      *
504      * @hide
505      */
506     @Nullable
507     @SystemApi
508     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getOccupantForAudioZoneId(int audioZoneId)509     public OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) {
510         try {
511             return mService.getOccupantForAudioZoneId(audioZoneId);
512         } catch (RemoteException e) {
513             return handleRemoteExceptionFromCarService(e, null);
514         }
515     }
516 
517     /**
518      * Returns assigned display type for the display. It will return {@link #DISPLAY_TYPE_UNKNOWN}
519      * if type is not specified or if display is no longer available.
520      */
521     @DisplayTypeEnum
getDisplayType(@onNull Display display)522     public int getDisplayType(@NonNull Display display) {
523         assertNonNullDisplay(display);
524         try {
525             return mService.getDisplayType(display.getDisplayId());
526         } catch (RemoteException e) {
527             return handleRemoteExceptionFromCarService(e, DISPLAY_TYPE_UNKNOWN);
528         }
529     }
530 
531     /**
532      * Returns android user id assigned for the given zone. It will return
533      * {@link #INVALID_USER_ID} if user is not assigned or if zone is not available.
534      */
535     @UserIdInt
getUserForOccupant(@onNull OccupantZoneInfo occupantZone)536     public int getUserForOccupant(@NonNull OccupantZoneInfo occupantZone) {
537         assertNonNullOccupant(occupantZone);
538         try {
539             return mService.getUserForOccupant(occupantZone.zoneId);
540         } catch (RemoteException e) {
541             return handleRemoteExceptionFromCarService(e, INVALID_USER_ID);
542         }
543     }
544 
545     /**
546      * Returns assigned user id for the given display id.
547      *
548      * @param displayId Should be valid display id. Passing invalid display id will lead into
549      *        getting {@link #INVALID_USER_ID} result.
550      * @return Valid user id or {@link #INVALID_USER_ID} if no user is assigned for the display.
551      */
552     @UserIdInt
getUserForDisplayId(int displayId)553     public int getUserForDisplayId(int displayId) {
554         try {
555             return mService.getUserForDisplayId(displayId);
556         } catch (RemoteException e) {
557             return handleRemoteExceptionFromCarService(e, INVALID_USER_ID);
558         }
559     }
560 
561     /**
562      * Returns the info for the occupant zone that has the display identified by the given
563      * {@code displayId}.
564      *
565      * @param displayId Should be valid display id. Passing in invalid display id will lead into
566      *        getting {@code null} occupant zone info result.
567      * @return Occupant zone info or {@code null} if no occupant zone is found which has the given
568      * display.
569      *
570      * @hide
571      */
572     @SystemApi
573     @Nullable
getOccupantZoneForDisplayId(int displayId)574     public OccupantZoneInfo getOccupantZoneForDisplayId(int displayId) {
575         try {
576             return mService.getOccupantZoneForDisplayId(displayId);
577         } catch (RemoteException e) {
578             return handleRemoteExceptionFromCarService(e, null);
579         }
580     }
581 
582     /**
583      * Assigns the given profile {@code userId} to the {@code occupantZone}. Returns true when the
584      * request succeeds.
585      *
586      * <p>Note that only non-driver zone can be assigned with this call. Calling this for driver
587      * zone will lead into {@code IllegalArgumentException}.
588      *
589      * @param occupantZone Zone to assign user.
590      * @param userId       profile user id to assign. Passing {@link #INVALID_USER_ID} leads into
591      *                     removing the current user assignment.
592      * @return true if the request succeeds or if the user is already assigned to the zone.
593      * @deprecated Use {@link #assignVisibleUserToOccupantZone(OccupantZoneInfo, UserHandle)}
594      * instead.
595      *
596      * @hide
597      */
598     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
599             Car.PERMISSION_MANAGE_OCCUPANT_ZONE})
600     @Deprecated
assignProfileUserToOccupantZone(@onNull OccupantZoneInfo occupantZone, @UserIdInt int userId)601     public boolean assignProfileUserToOccupantZone(@NonNull OccupantZoneInfo occupantZone,
602             @UserIdInt int userId) {
603         assertNonNullOccupant(occupantZone);
604         try {
605             return mService.assignProfileUserToOccupantZone(occupantZone.zoneId, userId);
606         } catch (RemoteException e) {
607             return handleRemoteExceptionFromCarService(e, false);
608         }
609     }
610 
611     /**
612      * Assign a visible user, which gets {@code true} from ({@link UserManager#isUserVisible()},
613      * to the specified occupant zone.
614      *
615      * <p>This API handles occupant zone change.
616      *
617      * <p>This API can take a long time, so it is recommended to call this from non-main thread.
618      *
619      * <p> The return value is {@link #USER_ASSIGNMENT_RESULT_OK} when the assignment succeeds or if
620      * the user is already allocated to the zone. Note that new error code can be added in the
621      * future. For now, following error codes will be returned for a Failure:
622      * <ul>
623      *   <li>{@link #USER_ASSIGNMENT_RESULT_FAIL_NON_VISIBLE_USER} for non-visible user.
624      *   <li>{@link #USER_ASSIGNMENT_RESULT_FAIL_ALREADY_ASSIGNED} if the user is already assigned
625      *       to other zone. New error code can be added in future.
626      *   <li>{@link #USER_ASSIGNMENT_RESULT_FAIL_DRIVER_ZONE} if non-current user is assigned to the
627      *       driver zone.
628      * </ul>
629      *
630      * <p>The system requires one user to  have one zone and moving user from one zone to another
631      * requires unassigning the zone using {@link #unassignOccupantZone(OccupantZoneInfo)}
632      * first.
633      *
634      * @param occupantZone the occupant zone to change user allocation
635      * @param user the user to allocate. {@code null} user removes the allocation for the zone
636      *             {@link UserHandle#CURRENT} will assign the current user to the zone
637      * @return Check the above
638      *
639      * @hide
640      */
641     @SystemApi
642     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
643             Car.PERMISSION_MANAGE_OCCUPANT_ZONE})
644     @UserAssignmentResult
assignVisibleUserToOccupantZone(@onNull OccupantZoneInfo occupantZone, @NonNull UserHandle user)645     public int assignVisibleUserToOccupantZone(@NonNull OccupantZoneInfo occupantZone,
646             @NonNull UserHandle user) {
647         assertNonNullOccupant(occupantZone);
648         try {
649             return mService.assignVisibleUserToOccupantZone(occupantZone.zoneId, user);
650         } catch (RemoteException e) {
651             // Return any error code if car service is gone.
652             return handleRemoteExceptionFromCarService(e,
653                     USER_ASSIGNMENT_RESULT_FAIL_ALREADY_ASSIGNED);
654         }
655     }
656 
657     /**
658      * Un-assign user from the specified occupant zone. The zone will return
659      * {@link #INVALID_USER_ID} for {@link #getUserForOccupant(OccupantZoneInfo)} after this call.
660      *
661      * @param occupantZone Zone to unassign.
662      * @return {@link #USER_ASSIGNMENT_RESULT_OK} if the zone is unassigned by the call or was
663      * already unassigned. Error code of {@link #USER_ASSIGNMENT_RESULT_FAIL_DRIVER_ZONE}
664      * will be returned if driver zone is asked.
665      *
666      * @hide
667      */
668     @SystemApi
669     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
670             Car.PERMISSION_MANAGE_OCCUPANT_ZONE})
671     @UserAssignmentResult
unassignOccupantZone(@onNull OccupantZoneInfo occupantZone)672     public int unassignOccupantZone(@NonNull OccupantZoneInfo occupantZone) {
673         try {
674             return mService.unassignOccupantZone(occupantZone.zoneId);
675         } catch (RemoteException e) {
676             // Return any error code if car service is gone.
677             return handleRemoteExceptionFromCarService(e, USER_ASSIGNMENT_RESULT_FAIL_DRIVER_ZONE);
678         }
679     }
680 
assertNonNullOccupant(OccupantZoneInfo occupantZone)681     private void assertNonNullOccupant(OccupantZoneInfo occupantZone) {
682         if (occupantZone == null) {
683             throw new IllegalArgumentException("null OccupantZoneInfo");
684         }
685     }
686 
assertNonNullDisplay(Display display)687     private void assertNonNullDisplay(Display display) {
688         if (display == null) {
689             throw new IllegalArgumentException("null Display");
690         }
691     }
692 
693     /**
694      * Registers the listener for occupant zone config change. Registering multiple listeners are
695      * allowed.
696      */
registerOccupantZoneConfigChangeListener( @onNull OccupantZoneConfigChangeListener listener)697     public void registerOccupantZoneConfigChangeListener(
698             @NonNull OccupantZoneConfigChangeListener listener) {
699         if (mListeners.addIfAbsent(listener)) {
700             if (mListeners.size() == 1) {
701                 try {
702                     mService.registerCallback(mBinderCallback);
703                 } catch (RemoteException e) {
704                     handleRemoteExceptionFromCarService(e);
705                 }
706             }
707         }
708     }
709 
710     /**
711      * Unregisters the listener. Listeners not registered before will be ignored.
712      */
unregisterOccupantZoneConfigChangeListener( @onNull OccupantZoneConfigChangeListener listener)713     public void unregisterOccupantZoneConfigChangeListener(
714             @NonNull OccupantZoneConfigChangeListener listener) {
715         if (mListeners.remove(listener)) {
716             if (mListeners.size() == 0) {
717                 try {
718                     mService.unregisterCallback(mBinderCallback);
719                 } catch (RemoteException ignored) {
720                     // ignore for unregistering
721                 }
722             }
723         }
724     }
725 
726     /**
727      * Returns {@link OccupantZoneInfo} for the calling process's android user.
728      * It will return {@code null} if there is no occupant zone assigned for the user.
729      *
730      * <p>When there is no occupant zone allocated for the user, most likely the user is not allowed
731      * to run Activity or play audio, which are the main use cases to get the zone. So apps should
732      * not try such tasks when {@code null} {@code OccupantZoneInfo} is returned. There can be an
733      * exception for system user running under
734      * {@link UserManager#isHeadlessSystemUserMode() Headless System User Mode}: The system user
735      * apps may show UI even if there is no zone allocated.
736      */
737     @Nullable
getMyOccupantZone()738     public OccupantZoneInfo getMyOccupantZone() {
739         try {
740             return mService.getMyOccupantZone();
741         } catch (RemoteException e) {
742             return handleRemoteExceptionFromCarService(e, null);
743         }
744     }
745 
746     /**
747      * Returns {@code OccupantZoneInfo} associated with the given {@code UserHandle}. In the case
748      * that the user is associated with multiple zones, this API returns the first matched zone.
749      *
750      * @param user The user to find.
751      * @return Matching occupant zone or {@code null} if the user is not assigned or user has a
752      * userId of {@code UserHandle#USER_NULL}.
753      */
754     @SuppressWarnings("UserHandle")
755     @Nullable
getOccupantZoneForUser(@onNull UserHandle user)756     public OccupantZoneInfo getOccupantZoneForUser(@NonNull UserHandle user) {
757         try {
758             return mService.getOccupantZoneForUser(user);
759         } catch (RemoteException e) {
760             return handleRemoteExceptionFromCarService(e, /* returnValue= */null);
761         }
762     }
763 
764     /**
765      * Finds {@code OccupantZoneInfo} for the given occupant type and seat.
766      * <p>For{@link #OCCUPANT_TYPE_DRIVER} and {@link #OCCUPANT_TYPE_FRONT_PASSENGER}, {@code seat}
767      * argument will be ignored.
768      *
769      * @param occupantType should be one of {@link #OCCUPANT_TYPE_DRIVER},
770      *                     {@link #OCCUPANT_TYPE_FRONT_PASSENGER},
771      *                     {@link #OCCUPANT_TYPE_FRONT_PASSENGER}
772      * @param seat         Seat of the occupant. This is necessary for
773      *                     {@link #OCCUPANT_TYPE_REAR_PASSENGER}.
774      * @return Matching occupant zone or {@code null} if such zone does not exist.
775      */
776     @Nullable
getOccupantZone(@ccupantTypeEnum int occupantType, @VehicleAreaSeat.Enum int seat)777     public OccupantZoneInfo getOccupantZone(@OccupantTypeEnum int occupantType,
778             @VehicleAreaSeat.Enum int seat) {
779         try {
780             return mService.getOccupantZone(occupantType, seat);
781         } catch (RemoteException e) {
782             return handleRemoteExceptionFromCarService(e, null);
783         }
784 
785     }
786 
787     /**
788      * Returns {@code true} if the system has a driver zone. It will return false for system with
789      * only passenger zones.
790      *
791      * <p> Note that at least one zone must be present and following system configurations are
792      * possible:
793      * <ul>
794      *     <li>One driver zone only.
795      *     <li>One driver zone with at least one passenger zone.
796      *     <li>At least one passenger zone.
797      * </ul>
798      */
hasDriverZone()799     public boolean hasDriverZone() {
800         try {
801             return mService.hasDriverZone();
802         } catch (RemoteException e) {
803             return handleRemoteExceptionFromCarService(e, false);
804         }
805     }
806 
807     /**
808      * Returns {@code true} if the system has front or rear passenger zones. Check
809      * {@link #hasDriverZone()} for possible system configurations.
810      */
hasPassengerZones()811     public boolean hasPassengerZones() {
812         try {
813             return mService.hasPassengerZones();
814         } catch (RemoteException e) {
815             return handleRemoteExceptionFromCarService(e, false);
816         }
817     }
818 
handleOnOccupantZoneConfigChanged(int flags)819     private void handleOnOccupantZoneConfigChanged(int flags) {
820         for (OccupantZoneConfigChangeListener listener : mListeners) {
821             listener.onOccupantZoneConfigChanged(flags);
822         }
823     }
824 
825     private final class EventHandler extends Handler {
826         private static final int MSG_ZONE_CHANGE = 1;
827 
EventHandler(Looper looper)828         private EventHandler(Looper looper) {
829             super(looper);
830         }
831 
832         @Override
handleMessage(Message msg)833         public void handleMessage(Message msg) {
834             switch (msg.what) {
835                 case MSG_ZONE_CHANGE:
836                     handleOnOccupantZoneConfigChanged(msg.arg1);
837                     break;
838                 default:
839                     Slog.e(TAG, "Unknown msg not handdled:" + msg.what);
840                     break;
841             }
842         }
843 
dispatchOnOccupantZoneConfigChanged(int flags)844         private void dispatchOnOccupantZoneConfigChanged(int flags) {
845             sendMessage(obtainMessage(MSG_ZONE_CHANGE, flags, 0));
846         }
847     }
848 
849     private static class ICarOccupantZoneCallbackImpl extends ICarOccupantZoneCallback.Stub {
850         private final WeakReference<CarOccupantZoneManager> mManager;
851 
ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager)852         private ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager) {
853             mManager = new WeakReference<>(manager);
854         }
855 
856         @Override
onOccupantZoneConfigChanged(int flags)857         public void onOccupantZoneConfigChanged(int flags) {
858             CarOccupantZoneManager manager = mManager.get();
859             if (manager != null) {
860                 manager.mEventHandler.dispatchOnOccupantZoneConfigChanged(flags);
861             }
862         }
863     }
864 
865     /** @hide */
866     @Override
onCarDisconnected()867     public void onCarDisconnected() {
868         // nothing to do
869     }
870 
871     /**
872      * Returns the supported input types for the occupant zone info and display type passed as
873      * the argument.
874      *
875      * <p>It returns an empty list if the input type is unknown. Starting in Android
876      * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, all associated occupant zones and
877      * display types in {@code config_occupant_display_mapping} must define at least one input type.
878      *
879      * <p>If the display doesn't have any input type associated, then it should return a list
880      * containing {@link android.car.input.CarInputManager#INPUT_TYPE_NONE} only.
881      *
882      * <p>This is the list of all available input types this method may return:
883      * <ul>
884      *   <li>{@link android.car.input.CarInputManager#INPUT_TYPE_ROTARY_NAVIGATION}</li>
885      *   <li>{@link android.car.input.CarInputManager#INPUT_TYPE_ROTARY_VOLUME}</li>
886      *   <li>{@link android.car.input.CarInputManager#INPUT_TYPE_DPAD_KEYS}</li>
887      *   <li>{@link android.car.input.CarInputManager#INPUT_TYPE_NAVIGATE_KEYS}</li>
888      *   <li>{@link android.car.input.CarInputManager#INPUT_TYPE_SYSTEM_NAVIGATE_KEYS}</li>
889      *   <li>{@link android.car.input.CarInputManager#INPUT_TYPE_CUSTOM_INPUT_EVENT}</li>
890      *   <li>{@link android.car.input.CarInputManager#INPUT_TYPE_TOUCH_SCREEN}</li>
891      *   <li>{@link android.car.input.CarInputManager#INPUT_TYPE_NONE}</li>
892      * </ul>
893      *
894      * @param occupantZoneInfo the occupant zone info of the supported input types to find
895      * @param displayType      the display type of the supported input types to find
896      * @return the supported input types for the occupant zone info and display type passed in as
897      * the argument (see the full list of supported input types in the above)
898      */
899     @NonNull
getSupportedInputTypes(@onNull OccupantZoneInfo occupantZoneInfo, @DisplayTypeEnum int displayType)900     public List<Integer> getSupportedInputTypes(@NonNull OccupantZoneInfo occupantZoneInfo,
901             @DisplayTypeEnum int displayType) {
902         assertNonNullOccupant(occupantZoneInfo);
903         List<Integer> inputTypes;
904         try {
905             int[] ints = mService.getSupportedInputTypes(occupantZoneInfo.zoneId, displayType);
906             inputTypes = Lists.asImmutableList(ints);
907         } catch (RemoteException e) {
908             inputTypes = handleRemoteExceptionFromCarService(e, Collections.emptyList());
909         }
910         return inputTypes;
911     }
912 }
913