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