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