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