Creates implementation for Concurrent displays
Adds the ability to transition to the concurrent display state, and then present content on that display. Also provides the ability to add/remove listeners to know when this feature is available Extension interface cl: ag/21127264 Bug: 251747141 Test: ExtensionRearDisplayPresentationTest Change-Id: I1edae8b084d3e3b3f66ce7ced7249812b97c5610
This commit is contained in:
committed by
Andrii Kulian
parent
65368b9f96
commit
d159371dbd
@@ -6207,6 +6207,18 @@
|
||||
different from the home screen wallpaper. -->
|
||||
<bool name="config_independentLockscreenLiveWallpaper">false</bool>
|
||||
|
||||
<!-- Device state that corresponds to concurrent display mode where the default display
|
||||
is the internal display. Public API for the feature is provided through Jetpack
|
||||
WindowManager.
|
||||
TODO(b/236022708) Move concurrent display state to device state config file
|
||||
-->
|
||||
<integer name="config_deviceStateConcurrentRearDisplay">-1</integer>
|
||||
|
||||
<!-- Physical display address that corresponds to the rear display in rear display mode
|
||||
and concurrent display mode. Used to get information about the display before
|
||||
entering the corresponding modes -->
|
||||
<string name="config_rearDisplayPhysicalAddress" translatable="false"></string>
|
||||
|
||||
<!-- List of certificate to be used for font fs-verity integrity verification -->
|
||||
<string-array translatable="false" name="config_fontManagerServiceCerts">
|
||||
</string-array>
|
||||
|
||||
@@ -4907,6 +4907,8 @@
|
||||
<java-symbol type="string" name="concurrent_display_notification_thermal_content"/>
|
||||
<java-symbol type="string" name="device_state_notification_turn_off_button"/>
|
||||
<java-symbol type="bool" name="config_independentLockscreenLiveWallpaper"/>
|
||||
<java-symbol type="integer" name="config_deviceStateConcurrentRearDisplay" />
|
||||
<java-symbol type="string" name="config_rearDisplayPhysicalAddress" />
|
||||
|
||||
<!-- For app language picker -->
|
||||
<java-symbol type="string" name="system_locale_title" />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Consumer<Integer>> mRearDisplayStatusListeners = new ArraySet<>();
|
||||
@GuardedBy("mLock")
|
||||
private final ArraySet<Consumer<ExtensionWindowAreaStatus>>
|
||||
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<ExtensionWindowAreaStatus> 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<ExtensionWindowAreaStatus> 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<ExtensionWindowAreaStatus> consumer) {}
|
||||
|
||||
@Override
|
||||
public void removeRearDisplayPresentationStatusListener(
|
||||
@NonNull Consumer<ExtensionWindowAreaStatus> 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,17 @@ 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) {
|
||||
synchronized (mLock) {
|
||||
for (int i = 0; i < mRearDisplayStatusListeners.size(); i++) {
|
||||
mRearDisplayStatusListeners.valueAt(i).accept(windowAreaStatus);
|
||||
@@ -238,26 +436,92 @@ 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) {
|
||||
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<Integer> mRearDisplaySessionCallback;
|
||||
|
||||
DeviceStateRequestCallbackAdapter(@NonNull Consumer<Integer> callback) {
|
||||
RearDisplayStateRequestCallbackAdapter(@NonNull Consumer<Integer> 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 +529,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user