1 /* 2 * Copyright (C) 2022 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 androidx.window.extensions.area; 18 19 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; 20 21 import android.app.Activity; 22 import android.content.Context; 23 import android.hardware.devicestate.DeviceState; 24 import android.hardware.devicestate.DeviceStateManager; 25 import android.hardware.devicestate.DeviceStateRequest; 26 import android.hardware.display.DisplayManager; 27 import android.util.ArraySet; 28 import android.util.DisplayMetrics; 29 import android.util.Pair; 30 import android.view.Display; 31 import android.view.DisplayAddress; 32 import android.view.Surface; 33 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 import androidx.window.extensions.WindowExtensions; 37 import androidx.window.extensions.core.util.function.Consumer; 38 39 import com.android.internal.R; 40 import com.android.internal.annotations.GuardedBy; 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.util.ArrayUtils; 43 44 import java.util.List; 45 import java.util.Objects; 46 import java.util.concurrent.Executor; 47 48 /** 49 * Reference implementation of androidx.window.extensions.area OEM interface for use with 50 * WindowManager Jetpack. 51 * 52 * This component currently supports Rear Display mode with the ability to add and remove 53 * status listeners for this mode. 54 * 55 * The public methods in this class are thread-safe. 56 **/ 57 public class WindowAreaComponentImpl implements WindowAreaComponent, 58 DeviceStateManager.DeviceStateCallback { 59 60 private static final int INVALID_DISPLAY_ADDRESS = -1; 61 private final Object mLock = new Object(); 62 63 @NonNull 64 private final DeviceStateManager mDeviceStateManager; 65 @NonNull 66 private final DisplayManager mDisplayManager; 67 @NonNull 68 private final Executor mExecutor; 69 70 @GuardedBy("mLock") 71 private final ArraySet<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>(); 72 @GuardedBy("mLock") 73 private final ArraySet<Consumer<ExtensionWindowAreaStatus>> 74 mRearDisplayPresentationStatusListeners = new ArraySet<>(); 75 private final int mRearDisplayState; 76 private final int mConcurrentDisplayState; 77 @NonNull 78 private final int[] mFoldedDeviceStates; 79 private long mRearDisplayAddress = INVALID_DISPLAY_ADDRESS; 80 @WindowAreaSessionState 81 private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; 82 83 @GuardedBy("mLock") 84 private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER; 85 @GuardedBy("mLock") 86 private int[] mCurrentSupportedDeviceStates; 87 88 @GuardedBy("mLock") 89 private DeviceStateRequest mRearDisplayStateRequest; 90 @GuardedBy("mLock") 91 private RearDisplayPresentationController mRearDisplayPresentationController; 92 93 @Nullable 94 @GuardedBy("mLock") 95 private DisplayMetrics mRearDisplayMetrics; 96 97 @WindowAreaSessionState 98 @GuardedBy("mLock") 99 private int mLastReportedRearDisplayPresentationStatus; 100 WindowAreaComponentImpl(@onNull Context context)101 public WindowAreaComponentImpl(@NonNull Context context) { 102 mDeviceStateManager = context.getSystemService(DeviceStateManager.class); 103 mDisplayManager = context.getSystemService(DisplayManager.class); 104 mExecutor = context.getMainExecutor(); 105 106 // TODO(b/329436166): Update the usage of device state manager API's 107 mCurrentSupportedDeviceStates = getSupportedStateIdentifiers( 108 mDeviceStateManager.getSupportedDeviceStates()); 109 mFoldedDeviceStates = context.getResources().getIntArray( 110 R.array.config_foldedDeviceStates); 111 112 // TODO(b/236022708) Move rear display state to device state config file 113 mRearDisplayState = context.getResources().getInteger( 114 R.integer.config_deviceStateRearDisplay); 115 116 mConcurrentDisplayState = context.getResources().getInteger( 117 R.integer.config_deviceStateConcurrentRearDisplay); 118 119 mDeviceStateManager.registerCallback(mExecutor, this); 120 mRearDisplayAddress = getRearDisplayAddress(context); 121 } 122 123 /** 124 * Adds a listener interested in receiving updates on the RearDisplayStatus 125 * of the device. Because this is being called from the OEM provided 126 * extensions, the result of the listener will be posted on the executor 127 * provided by the developer at the initial call site. 128 * 129 * Rear display mode moves the calling application to the display on the device that is 130 * facing the same direction as the rear cameras. This would be the cover display on a fold-in 131 * style device when the device is opened. 132 * 133 * Depending on the initial state of the device, the {@link Consumer} will receive either 134 * {@link WindowAreaComponent#STATUS_AVAILABLE} or 135 * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that 136 * state respectively. When the rear display feature is triggered, the status is updated to be 137 * {@link WindowAreaComponent#STATUS_ACTIVE}. 138 * TODO(b/240727590): Prefix with AREA_ 139 * 140 * @param consumer {@link Consumer} interested in receiving updates to the status of 141 * rear display mode. 142 */ 143 @Override addRearDisplayStatusListener( @onNull Consumer<@WindowAreaStatus Integer> consumer)144 public void addRearDisplayStatusListener( 145 @NonNull Consumer<@WindowAreaStatus Integer> consumer) { 146 synchronized (mLock) { 147 mRearDisplayStatusListeners.add(consumer); 148 149 // If current device state is still invalid, the initial value has not been provided. 150 if (mCurrentDeviceState == INVALID_DEVICE_STATE_IDENTIFIER) { 151 return; 152 } 153 consumer.accept(getCurrentRearDisplayModeStatus()); 154 } 155 } 156 157 /** 158 * Removes a listener no longer interested in receiving updates. 159 * @param consumer no longer interested in receiving updates to RearDisplayStatus 160 */ 161 @Override removeRearDisplayStatusListener( @onNull Consumer<@WindowAreaStatus Integer> consumer)162 public void removeRearDisplayStatusListener( 163 @NonNull Consumer<@WindowAreaStatus Integer> consumer) { 164 synchronized (mLock) { 165 mRearDisplayStatusListeners.remove(consumer); 166 } 167 } 168 169 /** 170 * Creates and starts a rear display session and provides updates to the 171 * callback provided. Because this is being called from the OEM provided 172 * extensions, the result of the listener will be posted on the executor 173 * provided by the developer at the initial call site. 174 * 175 * Rear display mode moves the calling application to the display on the device that is 176 * facing the same direction as the rear cameras. This would be the cover display on a fold-in 177 * style device when the device is opened. 178 * 179 * When rear display mode is enabled, a request is made to {@link DeviceStateManager} 180 * to override the device state to the state that corresponds to RearDisplay 181 * mode. When the {@link DeviceStateRequest} is activated, the provided {@link Consumer} is 182 * notified that the session is active by receiving 183 * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}. 184 * 185 * @param activity to provide updates to the client on 186 * the status of the Session 187 * @param rearDisplaySessionCallback to provide updates to the client on 188 * the status of the Session 189 */ 190 @Override startRearDisplaySession(@onNull Activity activity, @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback)191 public void startRearDisplaySession(@NonNull Activity activity, 192 @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) { 193 synchronized (mLock) { 194 if (mRearDisplayStateRequest != null) { 195 // Rear display session is already active 196 throw new IllegalStateException( 197 "Unable to start new rear display session as one is already active"); 198 } 199 mRearDisplayStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build(); 200 mDeviceStateManager.requestState( 201 mRearDisplayStateRequest, 202 mExecutor, 203 new RearDisplayStateRequestCallbackAdapter(rearDisplaySessionCallback) 204 ); 205 } 206 } 207 208 /** 209 * Ends the current rear display session and provides updates to the 210 * callback provided. Because this is being called from the OEM provided 211 * extensions, the result of the listener will be posted on the executor 212 * provided by the developer at the initial call site. 213 */ 214 @Override endRearDisplaySession()215 public void endRearDisplaySession() { 216 synchronized (mLock) { 217 if (mRearDisplayStateRequest != null || isRearDisplayActive()) { 218 mRearDisplayStateRequest = null; 219 mDeviceStateManager.cancelStateRequest(); 220 } 221 } 222 } 223 224 /** 225 * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing 226 * display was not found in the display list, but we have already computed the 227 * {@link DisplayMetrics} for that display, we return the cached value. If no display has been 228 * found, then we return an empty {@link DisplayMetrics} value. 229 * 230 * TODO(b/267563768): Update with guidance from Display team for missing displays. 231 * 232 * @since {@link WindowExtensions#VENDOR_API_LEVEL_3} 233 */ 234 @Override 235 @NonNull getRearDisplayMetrics()236 public DisplayMetrics getRearDisplayMetrics() { 237 DisplayMetrics rearDisplayMetrics = null; 238 239 // DISPLAY_CATEGORY_REAR displays are only available when you are in the concurrent 240 // display state, so we have to look through all displays to match the address 241 final Display[] displays = mDisplayManager.getDisplays( 242 DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); 243 244 245 for (int i = 0; i < displays.length; i++) { 246 DisplayAddress.Physical address = 247 (DisplayAddress.Physical) displays[i].getAddress(); 248 if (address != null && mRearDisplayAddress == address.getPhysicalDisplayId()) { 249 rearDisplayMetrics = new DisplayMetrics(); 250 final Display rearDisplay = displays[i]; 251 252 // We must always retrieve the metrics for the rear display regardless of if it is 253 // the default display or not. 254 rearDisplay.getRealMetrics(rearDisplayMetrics); 255 256 // TODO(b/287170025): This should be something like if (!rearDisplay.isEnabled) 257 // instead. Currently when the rear display is disabled, its state is STATE_OFF. 258 if (rearDisplay.getDisplayId() != Display.DEFAULT_DISPLAY) { 259 final Display defaultDisplay = mDisplayManager 260 .getDisplay(Display.DEFAULT_DISPLAY); 261 rotateRearDisplayMetricsIfNeeded(defaultDisplay.getRotation(), 262 rearDisplay.getRotation(), rearDisplayMetrics); 263 } 264 break; 265 } 266 } 267 268 synchronized (mLock) { 269 // Update the rear display metrics with our latest value if one was received 270 if (rearDisplayMetrics != null) { 271 mRearDisplayMetrics = rearDisplayMetrics; 272 } 273 274 return Objects.requireNonNullElseGet(mRearDisplayMetrics, DisplayMetrics::new); 275 } 276 } 277 278 /** 279 * Adds a listener interested in receiving updates on the RearDisplayPresentationStatus 280 * of the device. Because this is being called from the OEM provided 281 * extensions, the result of the listener will be posted on the executor 282 * provided by the developer at the initial call site. 283 * 284 * Rear display presentation mode is a feature where an {@link Activity} can present 285 * additional content on a device with a second display that is facing the same direction 286 * as the rear camera (i.e. the cover display on a fold-in style device). The calling 287 * {@link Activity} does not move, whereas in rear display mode it does. 288 * 289 * This listener receives a {@link Pair} with the first item being the 290 * {@link WindowAreaComponent.WindowAreaStatus} that corresponds to the current status of the 291 * feature, and the second being the {@link DisplayMetrics} of the display that would be 292 * presented to when the feature is active. 293 * 294 * Depending on the initial state of the device, the {@link Consumer} will receive either 295 * {@link WindowAreaComponent#STATUS_AVAILABLE} or 296 * {@link WindowAreaComponent#STATUS_UNAVAILABLE} for the status value of the {@link Pair} if 297 * the feature is supported or not in that state respectively. Rear display presentation mode is 298 * currently not supported when the device is folded. When the rear display presentation feature 299 * is triggered, the status is updated to be {@link WindowAreaComponent#STATUS_UNAVAILABLE}. 300 * TODO(b/240727590): Prefix with AREA_ 301 * 302 * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently 303 * enabled. 304 * 305 * @param consumer {@link Consumer} interested in receiving updates to the status of 306 * rear display presentation mode. 307 */ 308 @Override addRearDisplayPresentationStatusListener( @onNull Consumer<ExtensionWindowAreaStatus> consumer)309 public void addRearDisplayPresentationStatusListener( 310 @NonNull Consumer<ExtensionWindowAreaStatus> consumer) { 311 synchronized (mLock) { 312 mRearDisplayPresentationStatusListeners.add(consumer); 313 314 // If current device state is still invalid, the initial value has not been provided 315 if (mCurrentDeviceState == INVALID_DEVICE_STATE_IDENTIFIER) { 316 return; 317 } 318 @WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus(); 319 DisplayMetrics metrics = currentStatus == STATUS_UNSUPPORTED ? new DisplayMetrics() 320 : getRearDisplayMetrics(); 321 consumer.accept( 322 new RearDisplayPresentationStatus(currentStatus, metrics)); 323 } 324 } 325 326 /** 327 * Removes a listener no longer interested in receiving updates. 328 * @param consumer no longer interested in receiving updates to RearDisplayPresentationStatus 329 */ 330 @Override removeRearDisplayPresentationStatusListener( @onNull Consumer<ExtensionWindowAreaStatus> consumer)331 public void removeRearDisplayPresentationStatusListener( 332 @NonNull Consumer<ExtensionWindowAreaStatus> consumer) { 333 synchronized (mLock) { 334 mRearDisplayPresentationStatusListeners.remove(consumer); 335 } 336 } 337 338 /** 339 * Creates and starts a rear display presentation session and sends state updates to the 340 * consumer provided. This consumer will receive a constant represented by 341 * {@link WindowAreaSessionState} to represent the state of the current rear display 342 * session. It will be translated to a more friendly interface in the library. 343 * 344 * Because this is being called from the OEM provided extensions, the library 345 * will post the result of the listener on the executor provided by the developer. 346 * 347 * Rear display presentation mode refers to a feature where an {@link Activity} can present 348 * additional content on a device with a second display that is facing the same direction 349 * as the rear camera (i.e. the cover display on a fold-in style device). The calling 350 * {@link Activity} stays on the user-facing display. 351 * 352 * @param activity that the OEM implementation will use as a base 353 * context and to identify the source display area of the request. 354 * The reference to the activity instance must not be stored in the OEM 355 * implementation to prevent memory leaks. 356 * @param consumer to provide updates to the client on the status of the session 357 * @throws UnsupportedOperationException if this method is called when rear display presentation 358 * mode is not available. This could be to an incompatible device state or when 359 * another process is currently in this mode. 360 */ 361 @Override startRearDisplayPresentationSession(@onNull Activity activity, @NonNull Consumer<@WindowAreaSessionState Integer> consumer)362 public void startRearDisplayPresentationSession(@NonNull Activity activity, 363 @NonNull Consumer<@WindowAreaSessionState Integer> consumer) { 364 synchronized (mLock) { 365 if (mRearDisplayPresentationController != null) { 366 // Rear display presentation session is already active 367 throw new IllegalStateException( 368 "Unable to start new rear display presentation session as one is already " 369 + "active"); 370 } 371 if (getCurrentRearDisplayPresentationModeStatus() 372 != WindowAreaComponent.STATUS_AVAILABLE) { 373 throw new IllegalStateException( 374 "Unable to start new rear display presentation session as the feature is " 375 + "is not currently available"); 376 } 377 378 mRearDisplayPresentationController = new RearDisplayPresentationController(activity, 379 stateStatus -> { 380 synchronized (mLock) { 381 if (stateStatus == SESSION_STATE_INACTIVE) { 382 // If the last reported session status was VISIBLE 383 // then the ACTIVE state should be dispatched before INACTIVE 384 // due to not having a good mechanism to know when 385 // the content is no longer visible before it's fully removed 386 if (getLastReportedRearDisplayPresentationStatus() 387 == SESSION_STATE_CONTENT_VISIBLE) { 388 consumer.accept(SESSION_STATE_ACTIVE); 389 } 390 mRearDisplayPresentationController = null; 391 } 392 mLastReportedRearDisplayPresentationStatus = stateStatus; 393 consumer.accept(stateStatus); 394 } 395 }); 396 RearDisplayPresentationRequestCallback deviceStateCallback = 397 new RearDisplayPresentationRequestCallback(activity, 398 mRearDisplayPresentationController); 399 DeviceStateRequest concurrentDisplayStateRequest = DeviceStateRequest.newBuilder( 400 mConcurrentDisplayState).build(); 401 402 try { 403 mDeviceStateManager.requestState( 404 concurrentDisplayStateRequest, 405 mExecutor, 406 deviceStateCallback 407 ); 408 } catch (SecurityException e) { 409 // If a SecurityException occurs when invoking DeviceStateManager#requestState 410 // (e.g. if the caller is not in the foreground, or if it does not have the required 411 // permissions), we should first clean up our local state before re-throwing the 412 // SecurityException to the caller. Otherwise, subsequent attempts to 413 // startRearDisplayPresentationSession will always fail. 414 mRearDisplayPresentationController = null; 415 throw e; 416 } 417 } 418 } 419 420 /** 421 * Ends the current rear display presentation session and provides updates to the 422 * callback provided. When this is ended, the presented content from the calling 423 * {@link Activity} will also be removed from the rear facing display. 424 * Because this is being called from the OEM provided extensions, the result of the listener 425 * will be posted on the executor provided by the developer at the initial call site. 426 * 427 * Cancelling the {@link DeviceStateRequest} and exiting the rear display presentation state, 428 * will remove the presentation window from the cover display as the cover display is no longer 429 * enabled. 430 */ 431 @Override endRearDisplayPresentationSession()432 public void endRearDisplayPresentationSession() { 433 synchronized (mLock) { 434 if (mRearDisplayPresentationController != null) { 435 mDeviceStateManager.cancelStateRequest(); 436 } 437 } 438 } 439 440 @Nullable 441 @Override getRearDisplayPresentation()442 public ExtensionWindowAreaPresentation getRearDisplayPresentation() { 443 synchronized (mLock) { 444 ExtensionWindowAreaPresentation presentation = null; 445 if (mRearDisplayPresentationController != null) { 446 presentation = mRearDisplayPresentationController.getWindowAreaPresentation(); 447 } 448 return presentation; 449 } 450 } 451 452 @Override onSupportedStatesChanged(@onNull List<DeviceState> supportedStates)453 public void onSupportedStatesChanged(@NonNull List<DeviceState> supportedStates) { 454 synchronized (mLock) { 455 // TODO(b/329436166): Update the usage of device state manager API's 456 mCurrentSupportedDeviceStates = getSupportedStateIdentifiers(supportedStates); 457 updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); 458 updateRearDisplayPresentationStatusListeners( 459 getCurrentRearDisplayPresentationModeStatus()); 460 } 461 } 462 463 @Override onDeviceStateChanged(@onNull DeviceState state)464 public void onDeviceStateChanged(@NonNull DeviceState state) { 465 synchronized (mLock) { 466 // TODO(b/329436166): Update the usage of device state manager API's 467 mCurrentDeviceState = state.getIdentifier(); 468 updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); 469 updateRearDisplayPresentationStatusListeners( 470 getCurrentRearDisplayPresentationModeStatus()); 471 } 472 } 473 474 @GuardedBy("mLock") getCurrentRearDisplayModeStatus()475 private int getCurrentRearDisplayModeStatus() { 476 if (mRearDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) { 477 return WindowAreaComponent.STATUS_UNSUPPORTED; 478 } 479 480 if (!ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)) { 481 return WindowAreaComponent.STATUS_UNAVAILABLE; 482 } 483 484 if (isRearDisplayActive()) { 485 return WindowAreaComponent.STATUS_ACTIVE; 486 } 487 488 return WindowAreaComponent.STATUS_AVAILABLE; 489 } 490 491 // TODO(b/329436166): Remove and update the usage of device state manager API's getSupportedStateIdentifiers(@onNull List<DeviceState> states)492 private int[] getSupportedStateIdentifiers(@NonNull List<DeviceState> states) { 493 int[] identifiers = new int[states.size()]; 494 for (int i = 0; i < states.size(); i++) { 495 identifiers[i] = states.get(i).getIdentifier(); 496 } 497 return identifiers; 498 } 499 500 /** 501 * Helper method to determine if a rear display session is currently active by checking 502 * if the current device state is that which corresponds to {@code mRearDisplayState}. 503 * 504 * @return {@code true} if the device is in rear display state {@code false} if not 505 */ 506 @GuardedBy("mLock") isRearDisplayActive()507 private boolean isRearDisplayActive() { 508 return mCurrentDeviceState == mRearDisplayState; 509 } 510 511 @GuardedBy("mLock") updateRearDisplayStatusListeners(@indowAreaStatus int windowAreaStatus)512 private void updateRearDisplayStatusListeners(@WindowAreaStatus int windowAreaStatus) { 513 if (mRearDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) { 514 return; 515 } 516 synchronized (mLock) { 517 for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) { 518 mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus); 519 } 520 } 521 } 522 523 @GuardedBy("mLock") getCurrentRearDisplayPresentationModeStatus()524 private int getCurrentRearDisplayPresentationModeStatus() { 525 if (mConcurrentDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) { 526 return WindowAreaComponent.STATUS_UNSUPPORTED; 527 } 528 529 if (mCurrentDeviceState == mConcurrentDisplayState) { 530 return WindowAreaComponent.STATUS_ACTIVE; 531 } 532 533 if (!ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState) 534 || isDeviceFolded()) { 535 return WindowAreaComponent.STATUS_UNAVAILABLE; 536 } 537 return WindowAreaComponent.STATUS_AVAILABLE; 538 } 539 540 @GuardedBy("mLock") isDeviceFolded()541 private boolean isDeviceFolded() { 542 return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState); 543 } 544 545 @GuardedBy("mLock") updateRearDisplayPresentationStatusListeners( @indowAreaStatus int windowAreaStatus)546 private void updateRearDisplayPresentationStatusListeners( 547 @WindowAreaStatus int windowAreaStatus) { 548 if (mConcurrentDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) { 549 return; 550 } 551 RearDisplayPresentationStatus consumerValue = new RearDisplayPresentationStatus( 552 windowAreaStatus, getRearDisplayMetrics()); 553 synchronized (mLock) { 554 for (int i = 0; i < mRearDisplayPresentationStatusListeners.size(); i++) { 555 mRearDisplayPresentationStatusListeners.valueAt(i).accept(consumerValue); 556 } 557 } 558 } 559 getRearDisplayAddress(Context context)560 private long getRearDisplayAddress(Context context) { 561 String address = context.getResources().getString( 562 R.string.config_rearDisplayPhysicalAddress); 563 return address.isEmpty() ? INVALID_DISPLAY_ADDRESS : Long.parseLong(address); 564 } 565 566 @GuardedBy("mLock") 567 @WindowAreaSessionState getLastReportedRearDisplayPresentationStatus()568 private int getLastReportedRearDisplayPresentationStatus() { 569 return mLastReportedRearDisplayPresentationStatus; 570 } 571 572 @VisibleForTesting rotateRearDisplayMetricsIfNeeded( @urface.Rotation int defaultDisplayRotation, @Surface.Rotation int rearDisplayRotation, @NonNull DisplayMetrics inOutMetrics)573 static void rotateRearDisplayMetricsIfNeeded( 574 @Surface.Rotation int defaultDisplayRotation, 575 @Surface.Rotation int rearDisplayRotation, 576 @NonNull DisplayMetrics inOutMetrics) { 577 // If the rear display has a non-zero rotation, it means the backing DisplayContent / 578 // DisplayRotation is fresh. 579 if (rearDisplayRotation != Surface.ROTATION_0) { 580 return; 581 } 582 583 // If the default display is 0 or 180, the rear display must also be 0 or 180. 584 if (defaultDisplayRotation == Surface.ROTATION_0 585 || defaultDisplayRotation == Surface.ROTATION_180) { 586 return; 587 } 588 589 final int heightPixels = inOutMetrics.heightPixels; 590 final int widthPixels = inOutMetrics.widthPixels; 591 inOutMetrics.widthPixels = heightPixels; 592 inOutMetrics.heightPixels = widthPixels; 593 594 final int noncompatHeightPixels = inOutMetrics.noncompatHeightPixels; 595 final int noncompatWidthPixels = inOutMetrics.noncompatWidthPixels; 596 inOutMetrics.noncompatWidthPixels = noncompatHeightPixels; 597 inOutMetrics.noncompatHeightPixels = noncompatWidthPixels; 598 } 599 600 /** 601 * Callback for the {@link DeviceStateRequest} to be notified of when the request has been 602 * activated or cancelled. This callback provides information to the client library 603 * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback} 604 */ 605 private class RearDisplayStateRequestCallbackAdapter implements DeviceStateRequest.Callback { 606 607 private final Consumer<Integer> mRearDisplaySessionCallback; 608 RearDisplayStateRequestCallbackAdapter(@onNull Consumer<Integer> callback)609 RearDisplayStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) { 610 mRearDisplaySessionCallback = callback; 611 } 612 613 @Override onRequestActivated(@onNull DeviceStateRequest request)614 public void onRequestActivated(@NonNull DeviceStateRequest request) { 615 synchronized (mLock) { 616 if (request.equals(mRearDisplayStateRequest)) { 617 mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE; 618 mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus); 619 } 620 } 621 } 622 623 @Override onRequestCanceled(DeviceStateRequest request)624 public void onRequestCanceled(DeviceStateRequest request) { 625 synchronized (mLock) { 626 if (request.equals(mRearDisplayStateRequest)) { 627 mRearDisplayStateRequest = null; 628 } 629 mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; 630 mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus); 631 } 632 } 633 } 634 } 635