diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e1b50d6e1f8b0..82de7b8ec77e8 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6210,6 +6210,18 @@ different from the home screen wallpaper. --> false + + -1 + + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 233813edaf604..aeb46cc188150 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4907,6 +4907,8 @@ + + diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java new file mode 100644 index 0000000000000..1ff169433b9d4 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.area; + +import android.app.Presentation; +import android.content.Context; +import android.view.Display; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.window.extensions.core.util.function.Consumer; + +/** + * {@link Presentation} object that is used to present extra content + * on the rear facing display when in a rear display presentation feature. + */ +class RearDisplayPresentation extends Presentation implements ExtensionWindowAreaPresentation { + + @NonNull + private final Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> mStateConsumer; + + RearDisplayPresentation(@NonNull Context outerContext, @NonNull Display display, + @NonNull Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> stateConsumer) { + super(outerContext, display); + mStateConsumer = stateConsumer; + } + + /** + * {@code mStateConsumer} is notified that their content is now visible when the + * {@link Presentation} object is started. There is no comparable callback for + * {@link WindowAreaComponent#SESSION_STATE_INVISIBLE} in {@link #onStop()} due to the + * timing of when a {@link android.hardware.devicestate.DeviceStateRequest} is cancelled + * ending rear display presentation mode happening before the {@link Presentation} is stopped. + */ + @Override + protected void onStart() { + super.onStart(); + mStateConsumer.accept(WindowAreaComponent.SESSION_STATE_VISIBLE); + } + + @NonNull + @Override + public Context getPresentationContext() { + return getContext(); + } + + @Override + public void setPresentationView(View view) { + setContentView(view); + if (!isShowing()) { + show(); + } + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java new file mode 100644 index 0000000000000..141a6ad487719 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationController.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.area; + +import static androidx.window.extensions.area.WindowAreaComponent.SESSION_STATE_ACTIVE; +import static androidx.window.extensions.area.WindowAreaComponent.SESSION_STATE_INACTIVE; + +import android.content.Context; +import android.hardware.devicestate.DeviceStateRequest; +import android.hardware.display.DisplayManager; +import android.util.Log; +import android.view.Display; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.window.extensions.core.util.function.Consumer; + +import java.util.Objects; + +/** + * Controller class that keeps track of the status of the device state request + * to enable the rear display presentation feature. This controller notifies the session callback + * when the state request is active, and notifies the callback when the request is canceled. + * + * Clients are notified via {@link Consumer} provided with + * {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus} values to signify + * when the request becomes active and cancelled. + */ +class RearDisplayPresentationController implements DeviceStateRequest.Callback { + + private static final String TAG = "RearDisplayPresentationController"; + + // Original context that requested to enable rear display presentation mode + @NonNull + private final Context mContext; + @NonNull + private final Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> mStateConsumer; + @Nullable + private ExtensionWindowAreaPresentation mExtensionWindowAreaPresentation; + @NonNull + private final DisplayManager mDisplayManager; + + /** + * Creates the RearDisplayPresentationController + * @param context Originating {@link android.content.Context} that is initiating the rear + * display presentation session. + * @param stateConsumer {@link Consumer} that will be notified that the session is active when + * the device state request is active and the session has been created. If the device + * state request is cancelled, the callback will be notified that the session has been + * ended. This could occur through a call to cancel the feature or if the device is + * manipulated in a way that cancels any device state override. + */ + RearDisplayPresentationController(@NonNull Context context, + @NonNull Consumer<@WindowAreaComponent.WindowAreaSessionState Integer> stateConsumer) { + Objects.requireNonNull(context); + Objects.requireNonNull(stateConsumer); + + mContext = context; + mStateConsumer = stateConsumer; + mDisplayManager = context.getSystemService(DisplayManager.class); + } + + @Override + public void onRequestActivated(@NonNull DeviceStateRequest request) { + Display[] rearDisplays = mDisplayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_REAR); + if (rearDisplays.length == 0) { + mStateConsumer.accept(SESSION_STATE_INACTIVE); + Log.e(TAG, "Rear display list should not be empty"); + return; + } + + mExtensionWindowAreaPresentation = + new RearDisplayPresentation(mContext, rearDisplays[0], mStateConsumer); + mStateConsumer.accept(SESSION_STATE_ACTIVE); + } + + @Override + public void onRequestCanceled(@NonNull DeviceStateRequest request) { + mStateConsumer.accept(SESSION_STATE_INACTIVE); + } + + @Nullable + public ExtensionWindowAreaPresentation getWindowAreaPresentation() { + return mExtensionWindowAreaPresentation; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java new file mode 100644 index 0000000000000..0b1423ae48c02 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentationStatus.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.area; + +import android.util.DisplayMetrics; + +import androidx.annotation.NonNull; + +/** + * Class that provides information around the current status of a window area feature. Contains + * the current {@link WindowAreaComponent.WindowAreaStatus} value corresponding to the + * rear display presentation feature, as well as the {@link DisplayMetrics} for the rear facing + * display. + */ +class RearDisplayPresentationStatus implements ExtensionWindowAreaStatus { + + @WindowAreaComponent.WindowAreaStatus + private final int mWindowAreaStatus; + + @NonNull + private final DisplayMetrics mDisplayMetrics; + + RearDisplayPresentationStatus(@WindowAreaComponent.WindowAreaStatus int status, + @NonNull DisplayMetrics displayMetrics) { + mWindowAreaStatus = status; + mDisplayMetrics = displayMetrics; + } + + /** + * Returns the {@link androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus} + * value that relates to the current status of a feature. + */ + @Override + @WindowAreaComponent.WindowAreaStatus + public int getWindowAreaStatus() { + return mWindowAreaStatus; + } + + /** + * Returns the {@link DisplayMetrics} that corresponds to the window area that a feature + * interacts with. This is converted to size class information provided to developers. + */ + @Override + @NonNull + public DisplayMetrics getWindowAreaDisplayMetrics() { + return mDisplayMetrics; + } +} diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 20602a1b290e1..274dcaee5a431 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -22,7 +22,12 @@ import android.app.Activity; import android.content.Context; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateRequest; +import android.hardware.display.DisplayManager; import android.util.ArraySet; +import android.util.DisplayMetrics; +import android.util.Pair; +import android.view.Display; +import android.view.DisplayAddress; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -30,6 +35,7 @@ import androidx.window.extensions.core.util.function.Consumer; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; import java.util.concurrent.Executor; @@ -47,61 +53,102 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, private final Object mLock = new Object(); + @NonNull private final DeviceStateManager mDeviceStateManager; + @NonNull + private final DisplayManager mDisplayManager; + @NonNull private final Executor mExecutor; @GuardedBy("mLock") private final ArraySet> mRearDisplayStatusListeners = new ArraySet<>(); + @GuardedBy("mLock") + private final ArraySet> + mRearDisplayPresentationStatusListeners = new ArraySet<>(); private final int mRearDisplayState; + private final int mConcurrentDisplayState; + @NonNull + private final int[] mFoldedDeviceStates; + @NonNull + private long mRearDisplayAddress = 0; @WindowAreaSessionState private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; @GuardedBy("mLock") private int mCurrentDeviceState = INVALID_DEVICE_STATE; @GuardedBy("mLock") - private int mCurrentDeviceBaseState = INVALID_DEVICE_STATE; + private int[] mCurrentSupportedDeviceStates; + @GuardedBy("mLock") - private DeviceStateRequest mDeviceStateRequest; + private DeviceStateRequest mRearDisplayStateRequest; + @GuardedBy("mLock") + private RearDisplayPresentationController mRearDisplayPresentationController; + + @Nullable + @GuardedBy("mLock") + private DisplayMetrics mRearDisplayMetrics; + + @WindowAreaSessionState + @GuardedBy("mLock") + private int mLastReportedRearDisplayPresentationStatus; public WindowAreaComponentImpl(@NonNull Context context) { mDeviceStateManager = context.getSystemService(DeviceStateManager.class); + mDisplayManager = context.getSystemService(DisplayManager.class); mExecutor = context.getMainExecutor(); + mCurrentSupportedDeviceStates = mDeviceStateManager.getSupportedStates(); + mFoldedDeviceStates = context.getResources().getIntArray( + R.array.config_foldedDeviceStates); + // TODO(b/236022708) Move rear display state to device state config file mRearDisplayState = context.getResources().getInteger( R.integer.config_deviceStateRearDisplay); + mConcurrentDisplayState = context.getResources().getInteger( + R.integer.config_deviceStateConcurrentRearDisplay); + mDeviceStateManager.registerCallback(mExecutor, this); + if (mConcurrentDisplayState != INVALID_DEVICE_STATE) { + mRearDisplayAddress = Long.parseLong(context.getResources().getString( + R.string.config_rearDisplayPhysicalAddress)); + } } /** * Adds a listener interested in receiving updates on the RearDisplayStatus * of the device. Because this is being called from the OEM provided - * extensions, we will post the result of the listener on the executor + * extensions, the result of the listener will be posted on the executor * provided by the developer at the initial call site. * - * Depending on the initial state of the device, we will return either + * Rear display mode moves the calling application to the display on the device that is + * facing the same direction as the rear cameras. This would be the cover display on a fold-in + * style device when the device is opened. + * + * Depending on the initial state of the device, the {@link Consumer} will receive either * {@link WindowAreaComponent#STATUS_AVAILABLE} or * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that - * state respectively. When the rear display feature is triggered, we update the status to be - * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. TODO(b/240727590) Prefix with AREA_ + * state respectively. When the rear display feature is triggered, the status is updated to be + * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. + * TODO(b/240727590): Prefix with AREA_ * - * TODO(b/239833099) Add a STATUS_ACTIVE option to let apps know if a feature is currently - * enabled. + * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently + * enabled. * * @param consumer {@link Consumer} interested in receiving updates to the status of * rear display mode. */ + @Override public void addRearDisplayStatusListener( @NonNull Consumer<@WindowAreaStatus Integer> consumer) { synchronized (mLock) { mRearDisplayStatusListeners.add(consumer); - // If current device state is still invalid, we haven't gotten our initial value yet + // If current device state is still invalid, the initial value has not been provided. if (mCurrentDeviceState == INVALID_DEVICE_STATE) { return; } - consumer.accept(getCurrentStatus()); + consumer.accept(getCurrentRearDisplayModeStatus()); } } @@ -109,6 +156,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, * Removes a listener no longer interested in receiving updates. * @param consumer no longer interested in receiving updates to RearDisplayStatus */ + @Override public void removeRearDisplayStatusListener( @NonNull Consumer<@WindowAreaStatus Integer> consumer) { synchronized (mLock) { @@ -119,13 +167,17 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, /** * Creates and starts a rear display session and provides updates to the * callback provided. Because this is being called from the OEM provided - * extensions, we will post the result of the listener on the executor + * extensions, the result of the listener will be posted on the executor * provided by the developer at the initial call site. * - * When we enable rear display mode, we submit a request to {@link DeviceStateManager} + * Rear display mode moves the calling application to the display on the device that is + * facing the same direction as the rear cameras. This would be the cover display on a fold-in + * style device when the device is opened. + * + * When rear display mode is enabled, a request is made to {@link DeviceStateManager} * to override the device state to the state that corresponds to RearDisplay - * mode. When the {@link DeviceStateRequest} is activated, we let the - * consumer know that the session is active by sending + * mode. When the {@link DeviceStateRequest} is activated, the provided {@link Consumer} is + * notified that the session is active by receiving * {@link WindowAreaComponent#SESSION_STATE_ACTIVE}. * * @param activity to provide updates to the client on @@ -133,19 +185,20 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, * @param rearDisplaySessionCallback to provide updates to the client on * the status of the Session */ + @Override public void startRearDisplaySession(@NonNull Activity activity, @NonNull Consumer<@WindowAreaSessionState Integer> rearDisplaySessionCallback) { synchronized (mLock) { - if (mDeviceStateRequest != null) { + if (mRearDisplayStateRequest != null) { // Rear display session is already active throw new IllegalStateException( "Unable to start new rear display session as one is already active"); } - mDeviceStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build(); + mRearDisplayStateRequest = DeviceStateRequest.newBuilder(mRearDisplayState).build(); mDeviceStateManager.requestState( - mDeviceStateRequest, + mRearDisplayStateRequest, mExecutor, - new DeviceStateRequestCallbackAdapter(rearDisplaySessionCallback) + new RearDisplayStateRequestCallbackAdapter(rearDisplaySessionCallback) ); } } @@ -153,13 +206,14 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, /** * Ends the current rear display session and provides updates to the * callback provided. Because this is being called from the OEM provided - * extensions, we will post the result of the listener on the executor - * provided by the developer. + * extensions, the result of the listener will be posted on the executor + * provided by the developer at the initial call site. */ + @Override public void endRearDisplaySession() { synchronized (mLock) { - if (mDeviceStateRequest != null || isRearDisplayActive()) { - mDeviceStateRequest = null; + if (mRearDisplayStateRequest != null || isRearDisplayActive()) { + mRearDisplayStateRequest = null; mDeviceStateManager.cancelStateRequest(); } else { throw new IllegalStateException( @@ -168,13 +222,176 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } } + /** + * Adds a listener interested in receiving updates on the RearDisplayPresentationStatus + * of the device. Because this is being called from the OEM provided + * extensions, the result of the listener will be posted on the executor + * provided by the developer at the initial call site. + * + * Rear display presentation mode is a feature where an {@link Activity} can present + * additional content on a device with a second display that is facing the same direction + * as the rear camera (i.e. the cover display on a fold-in style device). The calling + * {@link Activity} does not move, whereas in rear display mode it does. + * + * This listener receives a {@link Pair} with the first item being the + * {@link WindowAreaComponent.WindowAreaStatus} that corresponds to the current status of the + * feature, and the second being the {@link DisplayMetrics} of the display that would be + * presented to when the feature is active. + * + * Depending on the initial state of the device, the {@link Consumer} will receive either + * {@link WindowAreaComponent#STATUS_AVAILABLE} or + * {@link WindowAreaComponent#STATUS_UNAVAILABLE} for the status value of the {@link Pair} if + * the feature is supported or not in that state respectively. Rear display presentation mode is + * currently not supported when the device is folded. When the rear display presentation feature + * is triggered, the status is updated to be {@link WindowAreaComponent#STATUS_UNAVAILABLE}. + * TODO(b/240727590): Prefix with AREA_ + * + * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently + * enabled. + * + * @param consumer {@link Consumer} interested in receiving updates to the status of + * rear display presentation mode. + */ @Override - public void onBaseStateChanged(int state) { + public void addRearDisplayPresentationStatusListener( + @NonNull Consumer consumer) { synchronized (mLock) { - mCurrentDeviceBaseState = state; - if (state == mCurrentDeviceState) { - updateStatusConsumers(getCurrentStatus()); + mRearDisplayPresentationStatusListeners.add(consumer); + + // If current device state is still invalid, the initial value has not been provided + if (mCurrentDeviceState == INVALID_DEVICE_STATE) { + return; } + @WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus(); + consumer.accept( + new RearDisplayPresentationStatus(currentStatus, getRearDisplayMetrics())); + } + } + + /** + * Removes a listener no longer interested in receiving updates. + * @param consumer no longer interested in receiving updates to RearDisplayPresentationStatus + */ + @Override + public void removeRearDisplayPresentationStatusListener( + @NonNull Consumer consumer) { + synchronized (mLock) { + mRearDisplayPresentationStatusListeners.remove(consumer); + } + } + + /** + * Creates and starts a rear display presentation session and sends state updates to the + * consumer provided. This consumer will receive a constant represented by + * {@link WindowAreaSessionState} to represent the state of the current rear display + * session. It will be translated to a more friendly interface in the library. + * + * Because this is being called from the OEM provided extensions, the library + * will post the result of the listener on the executor provided by the developer. + * + * Rear display presentation mode refers to a feature where an {@link Activity} can present + * additional content on a device with a second display that is facing the same direction + * as the rear camera (i.e. the cover display on a fold-in style device). The calling + * {@link Activity} stays on the user-facing display. + * + * @param activity that the OEM implementation will use as a base + * context and to identify the source display area of the request. + * The reference to the activity instance must not be stored in the OEM + * implementation to prevent memory leaks. + * @param consumer to provide updates to the client on the status of the session + * @throws UnsupportedOperationException if this method is called when rear display presentation + * mode is not available. This could be to an incompatible device state or when + * another process is currently in this mode. + */ + @Override + public void startRearDisplayPresentationSession(@NonNull Activity activity, + @NonNull Consumer<@WindowAreaSessionState Integer> consumer) { + synchronized (mLock) { + if (mRearDisplayPresentationController != null) { + // Rear display presentation session is already active + throw new IllegalStateException( + "Unable to start new rear display presentation session as one is already " + + "active"); + } + if (getCurrentRearDisplayPresentationModeStatus() + != WindowAreaComponent.STATUS_AVAILABLE) { + throw new IllegalStateException( + "Unable to start new rear display presentation session as the feature is " + + "is not currently available"); + } + + mRearDisplayPresentationController = new RearDisplayPresentationController(activity, + stateStatus -> { + synchronized (mLock) { + if (stateStatus == SESSION_STATE_INACTIVE) { + // If the last reported session status was VISIBLE + // then the INVISIBLE state should be dispatched before INACTIVE + // due to not having a good mechanism to know when + // the content is no longer visible before it's fully removed + if (getLastReportedRearDisplayPresentationStatus() + == SESSION_STATE_VISIBLE) { + consumer.accept(SESSION_STATE_INVISIBLE); + } + mRearDisplayPresentationController = null; + } + mLastReportedRearDisplayPresentationStatus = stateStatus; + consumer.accept(stateStatus); + } + }); + + DeviceStateRequest concurrentDisplayStateRequest = DeviceStateRequest.newBuilder( + mConcurrentDisplayState).build(); + mDeviceStateManager.requestState( + concurrentDisplayStateRequest, + mExecutor, + mRearDisplayPresentationController + ); + } + } + + /** + * Ends the current rear display presentation session and provides updates to the + * callback provided. When this is ended, the presented content from the calling + * {@link Activity} will also be removed from the rear facing display. + * Because this is being called from the OEM provided extensions, the result of the listener + * will be posted on the executor provided by the developer at the initial call site. + * + * Cancelling the {@link DeviceStateRequest} and exiting the rear display presentation state, + * will remove the presentation window from the cover display as the cover display is no longer + * enabled. + */ + @Override + public void endRearDisplayPresentationSession() { + synchronized (mLock) { + if (mRearDisplayPresentationController != null) { + mDeviceStateManager.cancelStateRequest(); + } else { + throw new IllegalStateException( + "Unable to cancel a rear display presentation session as there is no " + + "active session"); + } + } + } + + @Nullable + @Override + public ExtensionWindowAreaPresentation getRearDisplayPresentation() { + synchronized (mLock) { + ExtensionWindowAreaPresentation presentation = null; + if (mRearDisplayPresentationController != null) { + presentation = mRearDisplayPresentationController.getWindowAreaPresentation(); + } + return presentation; + } + } + + @Override + public void onSupportedStatesChanged(int[] supportedStates) { + synchronized (mLock) { + mCurrentSupportedDeviceStates = supportedStates; + updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); + updateRearDisplayPresentationStatusListeners( + getCurrentRearDisplayPresentationModeStatus()); } } @@ -182,34 +399,17 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, public void onStateChanged(int state) { synchronized (mLock) { mCurrentDeviceState = state; - updateStatusConsumers(getCurrentStatus()); + updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); + updateRearDisplayPresentationStatusListeners( + getCurrentRearDisplayPresentationModeStatus()); } } - @Override - public void addRearDisplayPresentationStatusListener( - @NonNull Consumer consumer) {} - - @Override - public void removeRearDisplayPresentationStatusListener( - @NonNull Consumer consumer) {} - - @Override - public void startRearDisplayPresentationSession(@NonNull Activity activity, - @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {} - - @Override - public void endRearDisplayPresentationSession() {} - - @Override - @Nullable - public ExtensionWindowAreaPresentation getRearDisplayPresentation() { - return null; - } @GuardedBy("mLock") - private int getCurrentStatus() { + private int getCurrentRearDisplayModeStatus() { if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE + || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState) || isRearDisplayActive()) { return WindowAreaComponent.STATUS_UNAVAILABLE; } @@ -218,19 +418,20 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, /** * Helper method to determine if a rear display session is currently active by checking - * if the current device configuration matches that of rear display. This would be true - * if there is a device override currently active (base state != current state) and the current - * state is that which corresponds to {@code mRearDisplayState} - * @return {@code true} if the device is in rear display mode and {@code false} if not + * if the current device state is that which corresponds to {@code mRearDisplayState}. + * + * @return {@code true} if the device is in rear display state {@code false} if not */ @GuardedBy("mLock") private boolean isRearDisplayActive() { - return (mCurrentDeviceState != mCurrentDeviceBaseState) && (mCurrentDeviceState - == mRearDisplayState); + return mCurrentDeviceState == mRearDisplayState; } @GuardedBy("mLock") - private void updateStatusConsumers(@WindowAreaStatus int windowAreaStatus) { + private void updateRearDisplayStatusListeners(@WindowAreaStatus int windowAreaStatus) { + if (mRearDisplayState == INVALID_DEVICE_STATE) { + return; + } synchronized (mLock) { for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) { mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus); @@ -238,26 +439,95 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } } + @GuardedBy("mLock") + private int getCurrentRearDisplayPresentationModeStatus() { + if (mCurrentDeviceState == mConcurrentDisplayState + || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mConcurrentDisplayState) + || isDeviceFolded()) { + return WindowAreaComponent.STATUS_UNAVAILABLE; + } + return WindowAreaComponent.STATUS_AVAILABLE; + } + + @GuardedBy("mLock") + private boolean isDeviceFolded() { + return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState); + } + + @GuardedBy("mLock") + private void updateRearDisplayPresentationStatusListeners( + @WindowAreaStatus int windowAreaStatus) { + if (mConcurrentDisplayState == INVALID_DEVICE_STATE) { + return; + } + RearDisplayPresentationStatus consumerValue = new RearDisplayPresentationStatus( + windowAreaStatus, getRearDisplayMetrics()); + synchronized (mLock) { + for (int i = 0; i < mRearDisplayPresentationStatusListeners.size(); i++) { + mRearDisplayPresentationStatusListeners.valueAt(i).accept(consumerValue); + } + } + } + + /** + * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing + * display was not found in the display list, but we have already computed the + * {@link DisplayMetrics} for that display, we return the cached value. + * + * TODO(b/267563768): Update with guidance from Display team for missing displays. + * + * @throws IllegalArgumentException if the display is not found and there is no cached + * {@link DisplayMetrics} for this display. + */ + @GuardedBy("mLock") + private DisplayMetrics getRearDisplayMetrics() { + Display[] displays = mDisplayManager.getDisplays( + DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED); + for (int i = 0; i < displays.length; i++) { + DisplayAddress.Physical address = + (DisplayAddress.Physical) displays[i].getAddress(); + if (mRearDisplayAddress == address.getPhysicalDisplayId()) { + if (mRearDisplayMetrics == null) { + mRearDisplayMetrics = new DisplayMetrics(); + } + displays[i].getRealMetrics(mRearDisplayMetrics); + return mRearDisplayMetrics; + } + } + if (mRearDisplayMetrics != null) { + return mRearDisplayMetrics; + } else { + throw new IllegalArgumentException( + "No display found with the provided display address"); + } + } + + @GuardedBy("mLock") + @WindowAreaSessionState + private int getLastReportedRearDisplayPresentationStatus() { + return mLastReportedRearDisplayPresentationStatus; + } + /** * Callback for the {@link DeviceStateRequest} to be notified of when the request has been * activated or cancelled. This callback provides information to the client library * on the status of the RearDisplay session through {@code mRearDisplaySessionCallback} */ - private class DeviceStateRequestCallbackAdapter implements DeviceStateRequest.Callback { + private class RearDisplayStateRequestCallbackAdapter implements DeviceStateRequest.Callback { private final Consumer mRearDisplaySessionCallback; - DeviceStateRequestCallbackAdapter(@NonNull Consumer callback) { + RearDisplayStateRequestCallbackAdapter(@NonNull Consumer callback) { mRearDisplaySessionCallback = callback; } @Override public void onRequestActivated(@NonNull DeviceStateRequest request) { synchronized (mLock) { - if (request.equals(mDeviceStateRequest)) { + if (request.equals(mRearDisplayStateRequest)) { mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE; mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus); - updateStatusConsumers(getCurrentStatus()); + updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); } } } @@ -265,12 +535,12 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, @Override public void onRequestCanceled(DeviceStateRequest request) { synchronized (mLock) { - if (request.equals(mDeviceStateRequest)) { - mDeviceStateRequest = null; + if (request.equals(mRearDisplayStateRequest)) { + mRearDisplayStateRequest = null; } mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE; mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus); - updateStatusConsumers(getCurrentStatus()); + updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus()); } } } diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar index 378ad811bd22a..9596c2233ecc7 100644 Binary files a/libs/WindowManager/Jetpack/window-extensions-release.aar and b/libs/WindowManager/Jetpack/window-extensions-release.aar differ