• 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 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.car.annotation.AddedInOrBefore;
28 import android.car.builtin.os.UserManagerHelper;
29 import android.hardware.display.DisplayManager;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.Message;
34 import android.os.Parcel;
35 import android.os.Parcelable;
36 import android.os.RemoteException;
37 import android.os.UserHandle;
38 import android.util.Log;
39 import android.view.Display;
40 
41 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 import java.lang.annotation.ElementType;
45 import java.lang.annotation.Retention;
46 import java.lang.annotation.RetentionPolicy;
47 import java.lang.annotation.Target;
48 import java.lang.ref.WeakReference;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.List;
52 import java.util.concurrent.CopyOnWriteArrayList;
53 
54 /**
55  * API to get information on displays and users in the car.
56  */
57 public class CarOccupantZoneManager extends CarManagerBase {
58 
59     private static final String TAG = CarOccupantZoneManager.class.getSimpleName();
60 
61     /** Display type is not known. In some system, some displays may be just public display without
62      *  any additional information and such displays will be treated as unknown.
63      */
64     @AddedInOrBefore(majorVersion = 33)
65     public static final int DISPLAY_TYPE_UNKNOWN = 0;
66 
67     /** Main display users are interacting with. UI for the user will be launched to this display by
68      *  default. {@link Display#DEFAULT_DISPLAY} will be always have this type. But there can be
69      *  multiple of this type as each passenger can have their own main display.
70      */
71     @AddedInOrBefore(majorVersion = 33)
72     public static final int DISPLAY_TYPE_MAIN = 1;
73 
74     /** Instrument cluster display. This may exist only for driver. */
75     @AddedInOrBefore(majorVersion = 33)
76     public static final int DISPLAY_TYPE_INSTRUMENT_CLUSTER = 2;
77 
78     /** Head Up Display. This may exist only for driver. */
79     @AddedInOrBefore(majorVersion = 33)
80     public static final int DISPLAY_TYPE_HUD = 3;
81 
82     /** Dedicated display for showing IME for {@link #DISPLAY_TYPE_MAIN} */
83     @AddedInOrBefore(majorVersion = 33)
84     public static final int DISPLAY_TYPE_INPUT = 4;
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     @AddedInOrBefore(majorVersion = 33)
91     public static final int DISPLAY_TYPE_AUXILIARY = 5;
92 
93     /** @hide */
94     @Retention(RetentionPolicy.SOURCE)
95     @IntDef(prefix = "DISPLAY_TYPE_", value = {
96             DISPLAY_TYPE_UNKNOWN,
97             DISPLAY_TYPE_MAIN,
98             DISPLAY_TYPE_INSTRUMENT_CLUSTER,
99             DISPLAY_TYPE_HUD,
100             DISPLAY_TYPE_INPUT,
101             DISPLAY_TYPE_AUXILIARY,
102     })
103     @Target({ElementType.TYPE_USE})
104     public @interface DisplayTypeEnum {}
105 
106     /** @hide */
107     @AddedInOrBefore(majorVersion = 33)
108     public static final int OCCUPANT_TYPE_INVALID = -1;
109 
110     /** Represents driver. There can be only one driver for the system. */
111     @AddedInOrBefore(majorVersion = 33)
112     public static final int OCCUPANT_TYPE_DRIVER = 0;
113 
114     /** Represents front passengers who sits in front side of car. Most cars will have only
115      *  one passenger of this type but this can be multiple. */
116     @AddedInOrBefore(majorVersion = 33)
117     public static final int OCCUPANT_TYPE_FRONT_PASSENGER = 1;
118 
119     /** Represents passengers in rear seats. There can be multiple passengers of this type. */
120     @AddedInOrBefore(majorVersion = 33)
121     public static final int OCCUPANT_TYPE_REAR_PASSENGER = 2;
122 
123     /** @hide */
124     @Retention(RetentionPolicy.SOURCE)
125     @IntDef(prefix = "OCCUPANT_TYPE_", value = {
126             OCCUPANT_TYPE_DRIVER,
127             OCCUPANT_TYPE_FRONT_PASSENGER,
128             OCCUPANT_TYPE_REAR_PASSENGER,
129     })
130     @Target({ElementType.TYPE_USE})
131     public @interface OccupantTypeEnum {}
132 
133     /**
134      * Represents an occupant zone in a car.
135      *
136      * <p>Each occupant does not necessarily represent single person but it is for mapping to one
137      * set of displays. For example, for display located in center rear seat, both left and right
138      * side passengers may use it but it is abstracted as a single occupant zone.</p>
139      */
140     public static final class OccupantZoneInfo implements Parcelable {
141         /** @hide */
142         @AddedInOrBefore(majorVersion = 33)
143         public static final int INVALID_ZONE_ID = -1;
144         /**
145          * This is an unique id to distinguish each occupant zone.
146          *
147          * <p>This can be helpful to distinguish different zones when {@link #occupantType} and
148          * {@link #seat} are the same for multiple occupant / passenger zones.</p>
149          *
150          * <p>This id will remain the same for the same zone across configuration changes like
151          * user switching or display changes</p>
152          */
153         @AddedInOrBefore(majorVersion = 33)
154         public int zoneId;
155         /** Represents type of passenger */
156         @OccupantTypeEnum
157         @AddedInOrBefore(majorVersion = 33)
158         public final int occupantType;
159         /**
160          * Represents seat assigned for the occupant. In some system, this can have value of
161          * {@link VehicleAreaSeat#SEAT_UNKNOWN}.
162          */
163         @VehicleAreaSeat.Enum
164         @AddedInOrBefore(majorVersion = 33)
165         public final int seat;
166 
167         /** @hide */
OccupantZoneInfo(int zoneId, @OccupantTypeEnum int occupantType, @VehicleAreaSeat.Enum int seat)168         public OccupantZoneInfo(int zoneId, @OccupantTypeEnum int occupantType,
169                 @VehicleAreaSeat.Enum int seat) {
170             this.zoneId = zoneId;
171             this.occupantType = occupantType;
172             this.seat = seat;
173         }
174 
175         /** @hide */
OccupantZoneInfo(Parcel in)176         public OccupantZoneInfo(Parcel in) {
177             zoneId = in.readInt();
178             occupantType = in.readInt();
179             seat = in.readInt();
180         }
181 
182         @Override
183         @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
184         @AddedInOrBefore(majorVersion = 33)
describeContents()185         public int describeContents() {
186             return 0;
187         }
188 
189         @Override
190         @AddedInOrBefore(majorVersion = 33)
writeToParcel(Parcel dest, int flags)191         public void writeToParcel(Parcel dest, int flags) {
192             dest.writeInt(zoneId);
193             dest.writeInt(occupantType);
194             dest.writeInt(seat);
195         }
196 
197         @Override
198         @AddedInOrBefore(majorVersion = 33)
equals(Object other)199         public boolean equals(Object other) {
200             if (this == other) {
201                 return true;
202             }
203             if (!(other instanceof OccupantZoneInfo)) {
204                 return false;
205             }
206             OccupantZoneInfo that = (OccupantZoneInfo) other;
207             return zoneId == that.zoneId && occupantType == that.occupantType
208                     && seat == that.seat;
209         }
210 
211         @Override
212         @AddedInOrBefore(majorVersion = 33)
hashCode()213         public int hashCode() {
214             int hash = 23;
215             hash = hash * 17 + zoneId;
216             hash = hash * 17 + occupantType;
217             hash = hash * 17 + seat;
218             return hash;
219         }
220 
221         @AddedInOrBefore(majorVersion = 33)
222         public static final Parcelable.Creator<OccupantZoneInfo> CREATOR =
223                 new Parcelable.Creator<OccupantZoneInfo>() {
224             public OccupantZoneInfo createFromParcel(Parcel in) {
225                 return new OccupantZoneInfo(in);
226             }
227 
228             public OccupantZoneInfo[] newArray(int size) {
229                 return new OccupantZoneInfo[size];
230             }
231         };
232 
233         @Override
234         @AddedInOrBefore(majorVersion = 33)
toString()235         public String toString() {
236             StringBuilder b = new StringBuilder(64);
237             b.append("OccupantZoneInfo{zoneId=");
238             b.append(zoneId);
239             b.append(" type=");
240             b.append(occupantType);
241             b.append(" seat=");
242             b.append(Integer.toHexString(seat));
243             b.append("}");
244             return b.toString();
245         }
246     }
247 
248     /** Zone config change caused by display changes. A display could have been added / removed.
249      *  Besides change in display itself. this can lead into removal / addition of passenger zones.
250      */
251     @AddedInOrBefore(majorVersion = 33)
252     public static final int ZONE_CONFIG_CHANGE_FLAG_DISPLAY = 0x1;
253 
254     /** Zone config change caused by user change. Assigned user for passenger zones have changed. */
255     @AddedInOrBefore(majorVersion = 33)
256     public static final int ZONE_CONFIG_CHANGE_FLAG_USER = 0x2;
257 
258     /** Zone config change caused by audio zone change.
259      * Assigned audio zone for passenger zones have changed.
260      **/
261     @AddedInOrBefore(majorVersion = 33)
262     public static final int ZONE_CONFIG_CHANGE_FLAG_AUDIO = 0x4;
263 
264     /** @hide */
265     @IntDef(flag = true, prefix = { "ZONE_CONFIG_CHANGE_FLAG_" }, value = {
266             ZONE_CONFIG_CHANGE_FLAG_DISPLAY,
267             ZONE_CONFIG_CHANGE_FLAG_USER,
268             ZONE_CONFIG_CHANGE_FLAG_AUDIO,
269     })
270     @Retention(RetentionPolicy.SOURCE)
271     @interface ZoneConfigChangeFlags {}
272 
273     /**
274      * Listener to monitor any Occupant Zone configuration change. The configuration change can
275      * involve some displays removed or new displays added. Also it can happen when assigned user
276      * for any zone changes.
277      */
278     public interface OccupantZoneConfigChangeListener {
279 
280         /**
281          * Configuration for occupant zones has changed. Apps should re-check all
282          * occupant zone configs. This can be caused by events like user switching and
283          * display addition / removal.
284          *
285          * @param changeFlags Reason for the zone change.
286          */
287         @AddedInOrBefore(majorVersion = 33)
onOccupantZoneConfigChanged(@oneConfigChangeFlags int changeFlags)288         void onOccupantZoneConfigChanged(@ZoneConfigChangeFlags int changeFlags);
289     }
290 
291     private final DisplayManager mDisplayManager;
292     private final EventHandler mEventHandler;
293 
294     private final ICarOccupantZone mService;
295 
296     private final ICarOccupantZoneCallbackImpl mBinderCallback;
297 
298     private final CopyOnWriteArrayList<OccupantZoneConfigChangeListener> mListeners =
299             new CopyOnWriteArrayList<>();
300 
301     /** @hide */
302     @VisibleForTesting
CarOccupantZoneManager(Car car, IBinder service)303     public CarOccupantZoneManager(Car car, IBinder service) {
304         super(car);
305         mService = ICarOccupantZone.Stub.asInterface(service);
306         mBinderCallback = new ICarOccupantZoneCallbackImpl(this);
307         mDisplayManager = getContext().getSystemService(DisplayManager.class);
308         mEventHandler = new EventHandler(getEventHandler().getLooper());
309     }
310 
311     /**
312      * Returns all available occupants in the system. If no occupant zone is defined in the system
313      * or none is available at the moment, it will return empty list.
314      */
315     @NonNull
316     @AddedInOrBefore(majorVersion = 33)
getAllOccupantZones()317     public List<OccupantZoneInfo> getAllOccupantZones() {
318         try {
319             return mService.getAllOccupantZones();
320         } catch (RemoteException e) {
321             return handleRemoteExceptionFromCarService(e, Collections.emptyList());
322         }
323     }
324 
325     /**
326      * Returns all displays assigned for the given occupant zone. If no display is available for
327      * the passenger, it will return empty list.
328      */
329     @NonNull
330     @AddedInOrBefore(majorVersion = 33)
getAllDisplaysForOccupant(@onNull OccupantZoneInfo occupantZone)331     public List<Display> getAllDisplaysForOccupant(@NonNull OccupantZoneInfo occupantZone) {
332         assertNonNullOccupant(occupantZone);
333         try {
334             int[] displayIds = mService.getAllDisplaysForOccupantZone(occupantZone.zoneId);
335             ArrayList<Display> displays = new ArrayList<>(displayIds.length);
336             for (int i = 0; i < displayIds.length; i++) {
337                 // quick confidence check while getDisplay can still handle invalid display
338                 if (displayIds[i] == Display.INVALID_DISPLAY) {
339                     continue;
340                 }
341                 Display display = mDisplayManager.getDisplay(displayIds[i]);
342                 if (display != null) { // necessary as display list could have changed.
343                     displays.add(display);
344                 }
345             }
346             return displays;
347         } catch (RemoteException e) {
348             return handleRemoteExceptionFromCarService(e, Collections.emptyList());
349         }
350     }
351 
352     /**
353      * Gets the display for the occupant for the specified display type, or returns {@code null}
354      * if no display matches the requirements.
355      *
356      * @param displayType This should be a valid display type and passing
357      *                    {@link #DISPLAY_TYPE_UNKNOWN} will always lead into {@link null} return.
358      */
359     @Nullable
360     @AddedInOrBefore(majorVersion = 33)
getDisplayForOccupant(@onNull OccupantZoneInfo occupantZone, @DisplayTypeEnum int displayType)361     public Display getDisplayForOccupant(@NonNull OccupantZoneInfo occupantZone,
362             @DisplayTypeEnum int displayType) {
363         assertNonNullOccupant(occupantZone);
364         try {
365             int displayId = mService.getDisplayForOccupant(occupantZone.zoneId, displayType);
366             // quick confidence check while getDisplay can still handle invalid display
367             if (displayId == Display.INVALID_DISPLAY) {
368                 return null;
369             }
370             return mDisplayManager.getDisplay(displayId);
371         } catch (RemoteException e) {
372             return handleRemoteExceptionFromCarService(e, null);
373         }
374     }
375 
376     /**
377      * Returns the display id for the driver.
378      *
379      * <p>This method just returns the display id for the requested type. The returned display id
380      * may correspond to a private display and the client may not have access to it.
381      *
382      * @param displayType the display type
383      * @return the driver's display id or {@link Display#INVALID_DISPLAY} when no such display
384      * exists
385      *
386      * @hide
387      */
388     @SystemApi
389     @RequiresPermission(Car.ACCESS_PRIVATE_DISPLAY_ID)
390     @AddedInOrBefore(majorVersion = 33)
getDisplayIdForDriver(@isplayTypeEnum int displayType)391     public int getDisplayIdForDriver(@DisplayTypeEnum int displayType) {
392         try {
393             return mService.getDisplayIdForDriver(displayType);
394         } catch (RemoteException e) {
395             return handleRemoteExceptionFromCarService(e, Display.INVALID_DISPLAY);
396         }
397     }
398 
399     /**
400      * Gets the audio zone id for the occupant, or returns
401      * {@code CarAudioManager.INVALID_AUDIO_ZONE} if no audio zone matches the requirements.
402      * throws InvalidArgumentException if occupantZone does not exist.
403      *
404      * @hide
405      */
406     @SystemApi
407     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
408     @AddedInOrBefore(majorVersion = 33)
getAudioZoneIdForOccupant(@onNull OccupantZoneInfo occupantZone)409     public int getAudioZoneIdForOccupant(@NonNull OccupantZoneInfo occupantZone) {
410         assertNonNullOccupant(occupantZone);
411         try {
412             return mService.getAudioZoneIdForOccupant(occupantZone.zoneId);
413         } catch (RemoteException e) {
414             return handleRemoteExceptionFromCarService(e, null);
415         }
416     }
417 
418     /**
419      * Gets occupant for the audio zone id, or returns {@code null}
420      * if no audio zone matches the requirements.
421      *
422      * @hide
423      */
424     @Nullable
425     @SystemApi
426     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
427     @AddedInOrBefore(majorVersion = 33)
getOccupantForAudioZoneId(int audioZoneId)428     public OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) {
429         try {
430             return mService.getOccupantForAudioZoneId(audioZoneId);
431         } catch (RemoteException e) {
432             return handleRemoteExceptionFromCarService(e, null);
433         }
434     }
435 
436     /**
437      * Returns assigned display type for the display. It will return {@link #DISPLAY_TYPE_UNKNOWN}
438      * if type is not specified or if display is no longer available.
439      */
440     @DisplayTypeEnum
441     @AddedInOrBefore(majorVersion = 33)
getDisplayType(@onNull Display display)442     public int getDisplayType(@NonNull Display display) {
443         assertNonNullDisplay(display);
444         try {
445             return mService.getDisplayType(display.getDisplayId());
446         } catch (RemoteException e) {
447             return handleRemoteExceptionFromCarService(e, DISPLAY_TYPE_UNKNOWN);
448         }
449     }
450 
451     /**
452      * Returns android user id assigned for the given zone. It will return
453      * {@link UserHandle#USER_NULL} if user is not assigned or if zone is not available.
454      */
455     @UserIdInt
456     @AddedInOrBefore(majorVersion = 33)
getUserForOccupant(@onNull OccupantZoneInfo occupantZone)457     public int getUserForOccupant(@NonNull OccupantZoneInfo occupantZone) {
458         assertNonNullOccupant(occupantZone);
459         try {
460             return mService.getUserForOccupant(occupantZone.zoneId);
461         } catch (RemoteException e) {
462             return handleRemoteExceptionFromCarService(e, UserManagerHelper.USER_NULL);
463         }
464     }
465 
466     /**
467      * Assigns the given profile {@code userId} to the {@code occupantZone}. Returns true when the
468      * request succeeds.
469      *
470      * <p>Note that only non-driver zone can be assigned with this call. Calling this for driver
471      * zone will lead into {@code IllegalArgumentException}.
472      *
473      * @param occupantZone Zone to assign user.
474      * @param userId profile user id to assign. Passing {@link UserHandle#USER_NULL} leads into
475      *               removing the current user assignment.
476      * @return true if the request succeeds or if the user is already assigned to the zone.
477      *
478      * @hide
479      */
480     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
481     @AddedInOrBefore(majorVersion = 33)
assignProfileUserToOccupantZone(@onNull OccupantZoneInfo occupantZone, @UserIdInt int userId)482     public boolean assignProfileUserToOccupantZone(@NonNull OccupantZoneInfo occupantZone,
483             @UserIdInt int userId) {
484         assertNonNullOccupant(occupantZone);
485         try {
486             return mService.assignProfileUserToOccupantZone(occupantZone.zoneId, userId);
487         } catch (RemoteException e) {
488             return handleRemoteExceptionFromCarService(e, false);
489         }
490     }
491 
assertNonNullOccupant(OccupantZoneInfo occupantZone)492     private void assertNonNullOccupant(OccupantZoneInfo occupantZone) {
493         if (occupantZone == null) {
494             throw new IllegalArgumentException("null OccupantZoneInfo");
495         }
496     }
497 
assertNonNullDisplay(Display display)498     private void assertNonNullDisplay(Display display) {
499         if (display == null) {
500             throw new IllegalArgumentException("null Display");
501         }
502     }
503 
504     /**
505      * Registers the listener for occupant zone config change. Registering multiple listeners are
506      * allowed.
507      */
508     @AddedInOrBefore(majorVersion = 33)
registerOccupantZoneConfigChangeListener( @onNull OccupantZoneConfigChangeListener listener)509     public void registerOccupantZoneConfigChangeListener(
510             @NonNull OccupantZoneConfigChangeListener listener) {
511         if (mListeners.addIfAbsent(listener)) {
512             if (mListeners.size() == 1) {
513                 try {
514                     mService.registerCallback(mBinderCallback);
515                 } catch (RemoteException e) {
516                     handleRemoteExceptionFromCarService(e);
517                 }
518             }
519         }
520     }
521 
522     /**
523      * Unregisters the listener. Listeners not registered before will be ignored.
524      */
525     @AddedInOrBefore(majorVersion = 33)
unregisterOccupantZoneConfigChangeListener( @onNull OccupantZoneConfigChangeListener listener)526     public void unregisterOccupantZoneConfigChangeListener(
527             @NonNull OccupantZoneConfigChangeListener listener) {
528         if (mListeners.remove(listener)) {
529             if (mListeners.size() == 0) {
530                 try {
531                     mService.unregisterCallback(mBinderCallback);
532                 } catch (RemoteException ignored) {
533                     // ignore for unregistering
534                 }
535             }
536         }
537     }
538 
handleOnOccupantZoneConfigChanged(int flags)539     private void handleOnOccupantZoneConfigChanged(int flags) {
540         for (OccupantZoneConfigChangeListener listener : mListeners) {
541             listener.onOccupantZoneConfigChanged(flags);
542         }
543     }
544 
545     private final class EventHandler extends Handler {
546         private static final int MSG_ZONE_CHANGE = 1;
547 
EventHandler(Looper looper)548         private EventHandler(Looper looper) {
549             super(looper);
550         }
551 
552         @Override
handleMessage(Message msg)553         public void handleMessage(Message msg) {
554             switch (msg.what) {
555                 case MSG_ZONE_CHANGE:
556                     handleOnOccupantZoneConfigChanged(msg.arg1);
557                     break;
558                 default:
559                     Log.e(TAG, "Unknown msg not handdled:" + msg.what);
560                     break;
561             }
562         }
563 
dispatchOnOccupantZoneConfigChanged(int flags)564         private void dispatchOnOccupantZoneConfigChanged(int flags) {
565             sendMessage(obtainMessage(MSG_ZONE_CHANGE, flags, 0));
566         }
567     }
568 
569     private static class ICarOccupantZoneCallbackImpl extends ICarOccupantZoneCallback.Stub {
570         private final WeakReference<CarOccupantZoneManager> mManager;
571 
ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager)572         private ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager) {
573             mManager = new WeakReference<>(manager);
574         }
575 
576         @Override
onOccupantZoneConfigChanged(int flags)577         public void onOccupantZoneConfigChanged(int flags) {
578             CarOccupantZoneManager manager = mManager.get();
579             if (manager != null) {
580                 manager.mEventHandler.dispatchOnOccupantZoneConfigChanged(flags);
581             }
582         }
583     }
584 
585     /** @hide */
586     @Override
587     @AddedInOrBefore(majorVersion = 33)
onCarDisconnected()588     public void onCarDisconnected() {
589         // nothing to do
590     }
591 }
592