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