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 sanity 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 sanity 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      * Gets the audio zone id for the occupant, or returns
345      * {@code CarAudioManager.INVALID_AUDIO_ZONE} if no audio zone matches the requirements.
346      * throws InvalidArgumentException if occupantZone does not exist.
347      *
348      * @hide
349      */
350     @SystemApi
351     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getAudioZoneIdForOccupant(@onNull OccupantZoneInfo occupantZone)352     public int getAudioZoneIdForOccupant(@NonNull OccupantZoneInfo occupantZone) {
353         assertNonNullOccupant(occupantZone);
354         try {
355             return mService.getAudioZoneIdForOccupant(occupantZone.zoneId);
356         } catch (RemoteException e) {
357             return handleRemoteExceptionFromCarService(e, null);
358         }
359     }
360 
361     /**
362      * Gets occupant for the audio zone id, or returns {@code null}
363      * if no audio zone matches the requirements.
364      *
365      * @hide
366      */
367     @Nullable
368     @SystemApi
369     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getOccupantForAudioZoneId(int audioZoneId)370     public OccupantZoneInfo getOccupantForAudioZoneId(int audioZoneId) {
371         try {
372             return mService.getOccupantForAudioZoneId(audioZoneId);
373         } catch (RemoteException e) {
374             return handleRemoteExceptionFromCarService(e, null);
375         }
376     }
377 
378     /**
379      * Returns assigned display type for the display. It will return {@link #DISPLAY_TYPE_UNKNOWN}
380      * if type is not specified or if display is no longer available.
381      */
382     @DisplayTypeEnum
getDisplayType(@onNull Display display)383     public int getDisplayType(@NonNull Display display) {
384         assertNonNullDisplay(display);
385         try {
386             return mService.getDisplayType(display.getDisplayId());
387         } catch (RemoteException e) {
388             return handleRemoteExceptionFromCarService(e, DISPLAY_TYPE_UNKNOWN);
389         }
390     }
391 
392     /**
393      * Returns android user id assigned for the given zone. It will return
394      * {@link UserHandle#USER_NULL} if user is not assigned or if zone is not available.
395      */
396     @UserIdInt
getUserForOccupant(@onNull OccupantZoneInfo occupantZone)397     public int getUserForOccupant(@NonNull OccupantZoneInfo occupantZone) {
398         assertNonNullOccupant(occupantZone);
399         try {
400             return mService.getUserForOccupant(occupantZone.zoneId);
401         } catch (RemoteException e) {
402             return handleRemoteExceptionFromCarService(e, UserHandle.USER_NULL);
403         }
404     }
405 
406     /**
407      * Assigns the given profile {@code userId} to the {@code occupantZone}. Returns true when the
408      * request succeeds.
409      *
410      * <p>Note that only non-driver zone can be assigned with this call. Calling this for driver
411      * zone will lead into {@code IllegalArgumentException}.
412      *
413      * @param occupantZone Zone to assign user.
414      * @param userId profile user id to assign. Passing {@link UserHandle#USER_NULL} leads into
415      *               removing the current user assignment.
416      * @return true if the request succeeds or if the user is already assigned to the zone.
417      *
418      * @hide
419      */
420     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
assignProfileUserToOccupantZone(@onNull OccupantZoneInfo occupantZone, @UserIdInt int userId)421     public boolean assignProfileUserToOccupantZone(@NonNull OccupantZoneInfo occupantZone,
422             @UserIdInt int userId) {
423         assertNonNullOccupant(occupantZone);
424         try {
425             return mService.assignProfileUserToOccupantZone(occupantZone.zoneId, userId);
426         } catch (RemoteException e) {
427             return handleRemoteExceptionFromCarService(e, false);
428         }
429     }
430 
assertNonNullOccupant(OccupantZoneInfo occupantZone)431     private void assertNonNullOccupant(OccupantZoneInfo occupantZone) {
432         if (occupantZone == null) {
433             throw new IllegalArgumentException("null OccupantZoneInfo");
434         }
435     }
436 
assertNonNullDisplay(Display display)437     private void assertNonNullDisplay(Display display) {
438         if (display == null) {
439             throw new IllegalArgumentException("null Display");
440         }
441     }
442 
443     /**
444      * Registers the listener for occupant zone config change. Registering multiple listeners are
445      * allowed.
446      */
registerOccupantZoneConfigChangeListener( @onNull OccupantZoneConfigChangeListener listener)447     public void registerOccupantZoneConfigChangeListener(
448             @NonNull OccupantZoneConfigChangeListener listener) {
449         if (mListeners.addIfAbsent(listener)) {
450             if (mListeners.size() == 1) {
451                 try {
452                     mService.registerCallback(mBinderCallback);
453                 } catch (RemoteException e) {
454                     handleRemoteExceptionFromCarService(e);
455                 }
456             }
457         }
458     }
459 
460     /**
461      * Unregisters the listener. Listeners not registered before will be ignored.
462      */
unregisterOccupantZoneConfigChangeListener( @onNull OccupantZoneConfigChangeListener listener)463     public void unregisterOccupantZoneConfigChangeListener(
464             @NonNull OccupantZoneConfigChangeListener listener) {
465         if (mListeners.remove(listener)) {
466             if (mListeners.size() == 0) {
467                 try {
468                     mService.unregisterCallback(mBinderCallback);
469                 } catch (RemoteException ignored) {
470                     // ignore for unregistering
471                 }
472             }
473         }
474     }
475 
handleOnOccupantZoneConfigChanged(int flags)476     private void handleOnOccupantZoneConfigChanged(int flags) {
477         for (OccupantZoneConfigChangeListener listener : mListeners) {
478             listener.onOccupantZoneConfigChanged(flags);
479         }
480     }
481 
482     private final class EventHandler extends Handler {
483         private static final int MSG_ZONE_CHANGE = 1;
484 
EventHandler(Looper looper)485         private EventHandler(Looper looper) {
486             super(looper);
487         }
488 
489         @Override
handleMessage(Message msg)490         public void handleMessage(Message msg) {
491             switch (msg.what) {
492                 case MSG_ZONE_CHANGE:
493                     handleOnOccupantZoneConfigChanged(msg.arg1);
494                     break;
495                 default:
496                     Log.e(TAG, "Unknown msg not handdled:" + msg.what);
497                     break;
498             }
499         }
500 
dispatchOnOccupantZoneConfigChanged(int flags)501         private void dispatchOnOccupantZoneConfigChanged(int flags) {
502             sendMessage(obtainMessage(MSG_ZONE_CHANGE, flags, 0));
503         }
504     }
505 
506     private static class ICarOccupantZoneCallbackImpl extends ICarOccupantZoneCallback.Stub {
507         private final WeakReference<CarOccupantZoneManager> mManager;
508 
ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager)509         private ICarOccupantZoneCallbackImpl(CarOccupantZoneManager manager) {
510             mManager = new WeakReference<>(manager);
511         }
512 
513         @Override
onOccupantZoneConfigChanged(int flags)514         public void onOccupantZoneConfigChanged(int flags) {
515             CarOccupantZoneManager manager = mManager.get();
516             if (manager != null) {
517                 manager.mEventHandler.dispatchOnOccupantZoneConfigChanged(flags);
518             }
519         }
520     }
521 
522     /** @hide */
523     @Override
onCarDisconnected()524     public void onCarDisconnected() {
525         // nothing to do
526     }
527 }
528