blob: d479e6ec07384b56dd054f6857441fcca043d608 [file] [log] [blame]
/*
* Copyright (C) 2022 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 android.server.wm.jetpack;
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.PollingCheck.waitFor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
import android.os.PowerManager;
import android.platform.test.annotations.Presubmit;
import android.server.wm.DeviceStateUtils;
import android.server.wm.jetpack.utils.TestRearDisplayActivity;
import android.server.wm.jetpack.utils.WindowExtensionTestRule;
import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.window.extensions.area.WindowAreaComponent;
import androidx.window.extensions.area.WindowAreaComponent.WindowAreaSessionState;
import androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus;
import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
/**
* Tests for the {@link androidx.window.extensions.area.WindowAreaComponent} implementation
* of the rear display functionality provided on the device (and only if one is available).
*
* Build/Install/Run:
* atest CtsWindowManagerJetpackTestCases:ExtensionRearDisplayTest
*/
@LargeTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ExtensionRearDisplayTest extends WindowManagerJetpackTestBase implements
DeviceStateManager.DeviceStateCallback {
private static final int TIMEOUT = 2000;
private static final int INVALID_DEVICE_STATE = -1;
private TestRearDisplayActivity mActivity;
private WindowAreaComponent mWindowAreaComponent;
private int mCurrentDeviceState;
private int mCurrentDeviceBaseState;
private int[] mSupportedDeviceStates;
@WindowAreaStatus
private Integer mWindowAreaStatus;
@WindowAreaSessionState
private Integer mWindowAreaSessionState;
private int mRearDisplayState;
private final Context mInstrumentationContext = getInstrumentation().getTargetContext();
private final KeyguardManager mKeyguardManager = mInstrumentationContext.getSystemService(
KeyguardManager.class);
private final DeviceStateManager mDeviceStateManager = mInstrumentationContext
.getSystemService(DeviceStateManager.class);
private final Consumer<Integer> mStatusListener = (status) -> mWindowAreaStatus = status;
private final Consumer<Integer> mSessionStateListener = (sessionState) -> {
mWindowAreaSessionState = sessionState;
};
@Rule
public final WindowExtensionTestRule mWindowManagerJetpackTestRule =
new WindowExtensionTestRule(WindowAreaComponent.class);
@Before
@Override
public void setUp() {
super.setUp();
mWindowAreaComponent =
(WindowAreaComponent) mWindowManagerJetpackTestRule.getExtensionComponent();
mSupportedDeviceStates = mDeviceStateManager.getSupportedStates();
assumeTrue(mSupportedDeviceStates.length > 1);
// TODO(b/236022708) Move rear display state to device state config file
mRearDisplayState = getInstrumentation().getTargetContext().getResources()
.getInteger(Resources.getSystem()
.getIdentifier("config_deviceStateRearDisplay", "integer", "android"));
assumeTrue(mRearDisplayState != INVALID_DEVICE_STATE);
mDeviceStateManager.registerCallback(Runnable::run, this);
mWindowAreaComponent.addRearDisplayStatusListener(mStatusListener);
unlockDeviceIfNeeded();
mActivity = startActivityNewTask(TestRearDisplayActivity.class);
waitAndAssert(() -> mWindowAreaStatus != null);
}
@After
@Override
public void tearDown() {
super.tearDown();
if (mWindowAreaComponent != null) {
mWindowAreaComponent.removeRearDisplayStatusListener(mStatusListener);
try {
DeviceStateUtils.runWithControlDeviceStatePermission(
() -> mDeviceStateManager.cancelStateRequest());
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
/**
* Tests that the RearDisplay status listeners receive the correct {@link WindowAreaStatus}
* values.
*
* The test goes through all supported device states and verifies that the correct status is
* returned. If the state does not place the device in the active RearDisplay configuration
* (i.e. the base state of the device is different than the current state, and that current
* state is the RearDisplay state), then it should receive the
* {@link WindowAreaStatus#STATUS_AVAILABLE} value, otherwise it should receive the
* {@link WindowAreaStatus#STATUS_UNAVAILABLE} value.
*/
@ApiTest(apis = {
"androidx.window.extensions.area.WindowAreaComponent#addRearDisplayStatusListener",
"androidx.window.extensions.area.WindowAreaComponent#removeRearDisplayStatusListener"})
@Test
public void testRearDisplayStatusListeners() throws Throwable {
Set<Integer> requestedStates = new HashSet<>();
while (requestedStates.size() != mSupportedDeviceStates.length) {
int newState = determineNewState(mCurrentDeviceState, mSupportedDeviceStates,
requestedStates);
if (newState != INVALID_DEVICE_STATE) {
requestedStates.add(newState);
DeviceStateRequest request = DeviceStateRequest.newBuilder(newState).build();
DeviceStateUtils.runWithControlDeviceStatePermission(() ->
mDeviceStateManager.requestState(request, null, null));
waitAndAssert(() -> mCurrentDeviceState == newState);
// If the state does not put the device into the rear display configuration,
// then the listener should receive the STATUS_AVAILABLE value.
if (!isRearDisplayActive(mCurrentDeviceState, mCurrentDeviceBaseState)) {
waitAndAssert(
() -> mWindowAreaStatus == WindowAreaComponent.STATUS_AVAILABLE);
} else {
waitAndAssert(
() -> mWindowAreaStatus == WindowAreaComponent.STATUS_UNAVAILABLE);
}
}
}
}
/**
* Tests that you can start and end rear display mode. Verifies that the {@link Consumer} that
* is provided when calling {@link WindowAreaComponent#startRearDisplaySession} receives
* the {@link WindowAreaSessionState#SESSION_STATE_ACTIVE} value when starting the session
* and {@link WindowAreaSessionState#SESSION_STATE_INACTIVE} when ending the session.
*
* This test also verifies that the {@link android.app.Activity} is still visible when rear
* display mode is started, and that the activity received a configuration change when enabling
* and disabling rear display mode. This is verifiable due to the current generation of
* hardware and the fact that there are different screen sizes from the different displays.
*/
@ApiTest(apis = {
"androidx.window.extensions.area.WindowAreaComponent#startRearDisplaySession",
"androidx.window.extensions.area.WindowAreaComponent#endRearDisplaySession"})
@Test
public void testStartAndEndRearDisplaySession() throws Throwable {
assumeTrue(mWindowAreaStatus == WindowAreaComponent.STATUS_AVAILABLE);
assumeTrue(mCurrentDeviceState != mRearDisplayState);
mActivity.mConfigurationChanged = false;
// Running with CONTROL_DEVICE_STATE permission to bypass educational overlay
DeviceStateUtils.runWithControlDeviceStatePermission(() ->
mWindowAreaComponent.startRearDisplaySession(mActivity, mSessionStateListener));
waitAndAssert(() -> mActivity.mConfigurationChanged);
assertTrue(mWindowAreaSessionState != null
&& mWindowAreaSessionState == WindowAreaComponent.SESSION_STATE_ACTIVE);
assertEquals(mCurrentDeviceState, mRearDisplayState);
assertTrue(isActivityVisible(mActivity));
mActivity.mConfigurationChanged = false;
mWindowAreaComponent.endRearDisplaySession();
waitAndAssert(() -> mActivity.mConfigurationChanged);
assertEquals(WindowAreaComponent.SESSION_STATE_INACTIVE, (int) mWindowAreaSessionState);
assertTrue(isActivityVisible(mActivity));
// Cancelling rear display mode should cancel the override, so verifying that the
// device state is the same as the physical state of the device.
assertEquals(mCurrentDeviceState, mCurrentDeviceBaseState);
assertEquals(WindowAreaComponent.STATUS_AVAILABLE, (int) mWindowAreaStatus);
}
@Override
public void onBaseStateChanged(int state) {
mCurrentDeviceBaseState = state;
}
@Override
public void onStateChanged(int state) {
mCurrentDeviceState = state;
}
/**
* Returns the next state that we should request that isn't the current state and
* has not already been requested.
*/
private int determineNewState(int currentDeviceState, int[] statesToRequest,
Set<Integer> requestedStates) {
for (int state : statesToRequest) {
if (state != currentDeviceState && !requestedStates.contains(state)) {
return state;
}
}
return INVALID_DEVICE_STATE;
}
/**
* 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
*/
private boolean isRearDisplayActive(int currentDeviceState, int currentDeviceBaseState) {
return (currentDeviceState != currentDeviceBaseState)
&& (currentDeviceState == mRearDisplayState);
}
private void unlockDeviceIfNeeded() {
if (isKeyguardLocked() || !Objects.requireNonNull(
mInstrumentationContext.getSystemService(PowerManager.class)).isInteractive()) {
pressWakeupButton();
pressUnlockButton();
}
}
private boolean isKeyguardLocked() {
return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
}
private void waitAndAssert(PollingCheck.PollingCheckCondition condition) {
waitFor(TIMEOUT, condition);
}
}