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