blob: 1ec64b5ac27139f435252a1e1a99988432b6e9f5 [file] [log] [blame]
/*
* Copyright (C) 2017 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.am;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
import static android.server.am.ActivityManagerDisplayTestBase.ReportedDisplayMetrics.getDisplayMetrics;
import static android.server.am.ComponentNameUtils.getActivityName;
import static android.server.am.Components.VIRTUAL_DISPLAY_ACTIVITY;
import static android.server.am.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY;
import static android.server.am.Components.VirtualDisplayActivity.COMMAND_DESTROY_DISPLAY;
import static android.server.am.Components.VirtualDisplayActivity.COMMAND_RESIZE_DISPLAY;
import static android.server.am.Components.VirtualDisplayActivity
.KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.server.am.Components.VirtualDisplayActivity.KEY_COMMAND;
import static android.server.am.Components.VirtualDisplayActivity.KEY_COUNT;
import static android.server.am.Components.VirtualDisplayActivity.KEY_SHOW_SYSTEM_DECORATIONS;
import static android.server.am.Components.VirtualDisplayActivity.KEY_DENSITY_DPI;
import static android.server.am.Components.VirtualDisplayActivity.KEY_LAUNCH_TARGET_COMPONENT;
import static android.server.am.Components.VirtualDisplayActivity.KEY_PRESENTATION_DISPLAY;
import static android.server.am.Components.VirtualDisplayActivity.KEY_PUBLIC_DISPLAY;
import static android.server.am.Components.VirtualDisplayActivity.KEY_RESIZE_DISPLAY;
import static android.server.am.Components.VirtualDisplayActivity.VIRTUAL_DISPLAY_PREFIX;
import static android.server.am.StateLogger.log;
import static android.server.am.StateLogger.logAlways;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Settings;
import android.server.am.ActivityManagerState.ActivityDisplay;
import android.server.am.CommandSession.ActivitySession;
import android.server.am.CommandSession.ActivitySessionClient;
import android.server.am.settings.SettingsSession;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.util.Size;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Base class for ActivityManager display tests.
*
* @see ActivityManagerDisplayTests
* @see ActivityManagerDisplayLockedKeyguardTests
*/
public class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
static final int CUSTOM_DENSITY_DPI = 222;
private static final int INVALID_DENSITY_DPI = -1;
ActivityDisplay getDisplayState(int displayId) {
return getDisplayState(getDisplaysStates(), displayId);
}
ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int displayId) {
for (ActivityDisplay display : displays) {
if (display.mId == displayId) {
return display;
}
}
return null;
}
/** Return the display state with width, height, dpi. Always not default display. */
ActivityDisplay getDisplayState(List<ActivityDisplay> displays, int width, int height,
int dpi) {
for (ActivityDisplay display : displays) {
if (display.mId == DEFAULT_DISPLAY) {
continue;
}
final Configuration config = display.mFullConfiguration;
if (config.densityDpi == dpi && config.screenWidthDp == width
&& config.screenHeightDp == height) {
return display;
}
}
return null;
}
List<ActivityDisplay> getDisplaysStates() {
mAmWmState.getAmState().computeState();
return mAmWmState.getAmState().getDisplays();
}
/** Find the display that was not originally reported in oldDisplays and added in newDisplays */
List<ActivityDisplay> findNewDisplayStates(List<ActivityDisplay> oldDisplays,
List<ActivityDisplay> newDisplays) {
final ArrayList<ActivityDisplay> result = new ArrayList<>();
for (ActivityDisplay newDisplay : newDisplays) {
if (oldDisplays.stream().noneMatch(d -> d.mId == newDisplay.mId)) {
result.add(newDisplay);
}
}
return result;
}
public static class ReportedDisplayMetrics {
private static final String WM_SIZE = "wm size";
private static final String WM_DENSITY = "wm density";
private static final Pattern PHYSICAL_SIZE =
Pattern.compile("Physical size: (\\d+)x(\\d+)");
private static final Pattern OVERRIDE_SIZE =
Pattern.compile("Override size: (\\d+)x(\\d+)");
private static final Pattern PHYSICAL_DENSITY =
Pattern.compile("Physical density: (\\d+)");
private static final Pattern OVERRIDE_DENSITY =
Pattern.compile("Override density: (\\d+)");
@NonNull
final Size physicalSize;
final int physicalDensity;
@Nullable
final Size overrideSize;
@Nullable
final Integer overrideDensity;
/** Get physical and override display metrics from WM for specified display. */
public static ReportedDisplayMetrics getDisplayMetrics(int displayId) {
return new ReportedDisplayMetrics(executeShellCommand(WM_SIZE + " -d " + displayId)
+ executeShellCommand(WM_DENSITY + " -d " + displayId));
}
void setDisplayMetrics(final Size size, final int density) {
setSize(size);
setDensity(density);
}
void restoreDisplayMetrics() {
if (overrideSize != null) {
setSize(overrideSize);
} else {
executeShellCommand(WM_SIZE + " reset");
}
if (overrideDensity != null) {
setDensity(overrideDensity);
} else {
executeShellCommand(WM_DENSITY + " reset");
}
}
private void setSize(final Size size) {
executeShellCommand(WM_SIZE + " " + size.getWidth() + "x" + size.getHeight());
}
private void setDensity(final int density) {
executeShellCommand(WM_DENSITY + " " + density);
}
/** Get display size that WM operates with. */
public Size getSize() {
return overrideSize != null ? overrideSize : physicalSize;
}
/** Get density that WM operates with. */
int getDensity() {
return overrideDensity != null ? overrideDensity : physicalDensity;
}
private ReportedDisplayMetrics(final String lines) {
Matcher matcher = PHYSICAL_SIZE.matcher(lines);
assertTrue("Physical display size must be reported", matcher.find());
log(matcher.group());
physicalSize = new Size(
Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
matcher = PHYSICAL_DENSITY.matcher(lines);
assertTrue("Physical display density must be reported", matcher.find());
log(matcher.group());
physicalDensity = Integer.parseInt(matcher.group(1));
matcher = OVERRIDE_SIZE.matcher(lines);
if (matcher.find()) {
log(matcher.group());
overrideSize = new Size(
Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
} else {
overrideSize = null;
}
matcher = OVERRIDE_DENSITY.matcher(lines);
if (matcher.find()) {
log(matcher.group());
overrideDensity = Integer.parseInt(matcher.group(1));
} else {
overrideDensity = null;
}
}
}
protected void tapOnDisplayCenter(int displayId) {
final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(displayId);
final int x = displayMetrics.getSize().getWidth() / 2;
final int y = displayMetrics.getSize().getHeight() / 2;
tapOnDisplay(x, y, displayId);
}
public class VirtualDisplaySession implements AutoCloseable {
private int mDensityDpi = CUSTOM_DENSITY_DPI;
private boolean mLaunchInSplitScreen = false;
private boolean mCanShowWithInsecureKeyguard = false;
private boolean mPublicDisplay = false;
private boolean mResizeDisplay = true;
private boolean mShowSystemDecorations = false;
private boolean mPresentationDisplay = false;
private ComponentName mLaunchActivity = null;
private boolean mSimulateDisplay = false;
private boolean mMustBeCreated = true;
private Size mSimulationDisplaySize = new Size(1024 /* width */, 768 /* height */);
private boolean mVirtualDisplayCreated = false;
private final OverlayDisplayDevicesSession mOverlayDisplayDeviceSession =
new OverlayDisplayDevicesSession();
VirtualDisplaySession setDensityDpi(int densityDpi) {
mDensityDpi = densityDpi;
return this;
}
VirtualDisplaySession setLaunchInSplitScreen(boolean launchInSplitScreen) {
mLaunchInSplitScreen = launchInSplitScreen;
return this;
}
VirtualDisplaySession setCanShowWithInsecureKeyguard(boolean canShowWithInsecureKeyguard) {
mCanShowWithInsecureKeyguard = canShowWithInsecureKeyguard;
return this;
}
VirtualDisplaySession setPublicDisplay(boolean publicDisplay) {
mPublicDisplay = publicDisplay;
return this;
}
VirtualDisplaySession setResizeDisplay(boolean resizeDisplay) {
mResizeDisplay = resizeDisplay;
return this;
}
VirtualDisplaySession setShowSystemDecorations(boolean showSystemDecorations) {
mShowSystemDecorations = showSystemDecorations;
return this;
}
VirtualDisplaySession setPresentationDisplay(boolean presentationDisplay) {
mPresentationDisplay = presentationDisplay;
return this;
}
VirtualDisplaySession setLaunchActivity(ComponentName launchActivity) {
mLaunchActivity = launchActivity;
return this;
}
public VirtualDisplaySession setSimulateDisplay(boolean simulateDisplay) {
mSimulateDisplay = simulateDisplay;
return this;
}
VirtualDisplaySession setSimulationDisplaySize(int width, int height) {
mSimulationDisplaySize = new Size(width, height);
return this;
}
VirtualDisplaySession setMustBeCreated(boolean mustBeCreated) {
mMustBeCreated = mustBeCreated;
return this;
}
@Nullable
public ActivityDisplay createDisplay() throws Exception {
return createDisplays(1).stream().findFirst().orElse(null);
}
@NonNull
List<ActivityDisplay> createDisplays(int count) throws Exception {
if (mSimulateDisplay) {
return simulateDisplay();
} else {
return createVirtualDisplays(count);
}
}
void resizeDisplay() {
executeShellCommand(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
+ " -f 0x20000000" + " --es " + KEY_COMMAND + " " + COMMAND_RESIZE_DISPLAY);
}
@Override
public void close() throws Exception {
mOverlayDisplayDeviceSession.close();
if (mVirtualDisplayCreated) {
destroyVirtualDisplays();
mVirtualDisplayCreated = false;
}
}
/**
* Simulate new display.
* <pre>
* <code>mDensityDpi</code> provide custom density for the display.
* </pre>
* @return {@link ActivityDisplay} of newly created display.
*/
private List<ActivityDisplay> simulateDisplay() throws Exception {
final List<ActivityDisplay> originalDs = getDisplaysStates();
// Create virtual display with custom density dpi and specified size.
mOverlayDisplayDeviceSession.set(mSimulationDisplaySize + "/" + mDensityDpi);
return assertAndGetNewDisplays(1, originalDs);
}
/**
* Create new virtual display.
* <pre>
* <code>mDensityDpi</code> provide custom density for the display.
* <code>mLaunchInSplitScreen</code> start {@link VirtualDisplayActivity} to side from
* {@link LaunchingActivity} on primary display.
* <code>mCanShowWithInsecureKeyguard</code> allow showing content when device is
* showing an insecure keyguard.
* <code>mMustBeCreated</code> should assert if the display was or wasn't created.
* <code>mPublicDisplay</code> make display public.
* <code>mResizeDisplay</code> should resize display when surface size changes.
* <code>LaunchActivity</code> should launch test activity immediately after display
* creation.
* </pre>
* @param displayCount number of displays to be created.
* @return A list of {@link ActivityDisplay} that represent newly created displays.
* @throws Exception
*/
private List<ActivityDisplay> createVirtualDisplays(int displayCount) {
// Start an activity that is able to create virtual displays.
if (mLaunchInSplitScreen) {
getLaunchActivityBuilder()
.setToSide(true)
.setTargetActivity(VIRTUAL_DISPLAY_ACTIVITY)
.execute();
} else {
launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
}
mAmWmState.computeState(false /* compareTaskAndStackBounds */,
new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
mAmWmState.assertVisibility(VIRTUAL_DISPLAY_ACTIVITY, true /* visible */);
mAmWmState.assertFocusedActivity("Focus must be on virtual display host activity",
VIRTUAL_DISPLAY_ACTIVITY);
final List<ActivityDisplay> originalDS = getDisplaysStates();
// Create virtual display with custom density dpi.
final StringBuilder createVirtualDisplayCommand = new StringBuilder(
getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY))
.append(" -f 0x20000000")
.append(" --es " + KEY_COMMAND + " " + COMMAND_CREATE_DISPLAY);
if (mDensityDpi != INVALID_DENSITY_DPI) {
createVirtualDisplayCommand
.append(" --ei " + KEY_DENSITY_DPI + " ")
.append(mDensityDpi);
}
createVirtualDisplayCommand.append(" --ei " + KEY_COUNT + " ").append(displayCount)
.append(" --ez " + KEY_CAN_SHOW_WITH_INSECURE_KEYGUARD + " ")
.append(mCanShowWithInsecureKeyguard)
.append(" --ez " + KEY_PUBLIC_DISPLAY + " ").append(mPublicDisplay)
.append(" --ez " + KEY_RESIZE_DISPLAY + " ").append(mResizeDisplay)
.append(" --ez " + KEY_SHOW_SYSTEM_DECORATIONS + " ")
.append(mShowSystemDecorations)
.append(" --ez " + KEY_PRESENTATION_DISPLAY + " ").append(mPresentationDisplay);
if (mLaunchActivity != null) {
createVirtualDisplayCommand
.append(" --es " + KEY_LAUNCH_TARGET_COMPONENT + " ")
.append(getActivityName(mLaunchActivity));
}
executeShellCommand(createVirtualDisplayCommand.toString());
mVirtualDisplayCreated = true;
return assertAndGetNewDisplays(mMustBeCreated ? displayCount : -1, originalDS);
}
/**
* Destroy existing virtual display.
*/
void destroyVirtualDisplays() {
final String destroyVirtualDisplayCommand = getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)
+ " -f 0x20000000"
+ " --es " + KEY_COMMAND + " " + COMMAND_DESTROY_DISPLAY;
executeShellCommand(destroyVirtualDisplayCommand);
waitForDisplaysDestroyed();
}
private void waitForDisplaysDestroyed() {
for (int retry = 1; retry <= 5; retry++) {
if (!isHostedVirtualDisplayPresent()) {
return;
}
logAlways("Waiting for hosted displays destruction... retry=" + retry);
SystemClock.sleep(500);
}
fail("Waiting for hosted displays destruction failed.");
}
private boolean isHostedVirtualDisplayPresent() {
mAmWmState.computeState(true);
return mAmWmState.getWmState().getDisplays().stream().anyMatch(
d -> d.getName() != null && d.getName().contains(VIRTUAL_DISPLAY_PREFIX));
}
/**
* Wait for desired number of displays to be created and get their properties.
* @param newDisplayCount expected display count, -1 if display should not be created.
* @param originalDS display states before creation of new display(s).
* @return list of new displays, empty list if no new display is created.
*/
private List<ActivityDisplay> assertAndGetNewDisplays(int newDisplayCount,
List<ActivityDisplay> originalDS) {
final int originalDisplayCount = originalDS.size();
// Wait for the display(s) to be created and get configurations.
final List<ActivityDisplay> ds = getDisplayStateAfterChange(
originalDisplayCount + newDisplayCount);
if (newDisplayCount != -1) {
assertEquals("New virtual display(s) must be created",
originalDisplayCount + newDisplayCount, ds.size());
} else {
assertEquals("New virtual display must not be created",
originalDisplayCount, ds.size());
return Collections.emptyList();
}
// Find the newly added display(s).
final List<ActivityDisplay> newDisplays = findNewDisplayStates(originalDS, ds);
assertThat("New virtual display must be created",
newDisplays, hasSize(newDisplayCount));
return newDisplays;
}
}
// TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated.
protected class VirtualDisplayLauncher extends VirtualDisplaySession {
private final ActivitySessionClient mActivitySessionClient = new ActivitySessionClient();
ActivitySession launchActivityOnDisplay(ComponentName activityName,
ActivityDisplay display) {
return launchActivityOnDisplay(activityName, display, null /* extrasConsumer */,
true /* withShellPermission */, true /* waitForLaunch */);
}
ActivitySession launchActivityOnDisplay(ComponentName activityName,
ActivityDisplay display, Consumer<Bundle> extrasConsumer,
boolean withShellPermission, boolean waitForLaunch) {
return launchActivity(builder -> builder
// VirtualDisplayActivity is in different package. If the display is not public,
// it requires shell permission to launch activity ({@see com.android.server.am.
// ActivityStackSupervisor#isCallerAllowedToLaunchOnDisplay}).
.setWithShellPermission(withShellPermission)
.setWaitForLaunched(waitForLaunch)
.setIntentExtra(extrasConsumer)
.setTargetActivity(activityName)
.setDisplayId(display.mId));
}
ActivitySession launchActivity(Consumer<LaunchActivityBuilder> setupBuilder) {
final LaunchActivityBuilder builder = getLaunchActivityBuilder()
.setUseInstrumentation();
setupBuilder.accept(builder);
return mActivitySessionClient.startActivity(builder);
}
@Override
public void close() throws Exception {
super.close();
mActivitySessionClient.close();
}
}
/** Helper class to save, set, and restore overlay_display_devices preference. */
private static class OverlayDisplayDevicesSession extends SettingsSession<String> {
OverlayDisplayDevicesSession() {
super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
Settings.Global::getString,
Settings.Global::putString);
}
}
/** Wait for provided number of displays and report their configurations. */
List<ActivityDisplay> getDisplayStateAfterChange(int expectedDisplayCount) {
List<ActivityDisplay> ds = getDisplaysStates();
int retriesLeft = 5;
while (!areDisplaysValid(ds, expectedDisplayCount) && retriesLeft-- > 0) {
log("***Waiting for the correct number of displays...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log(e.toString());
}
ds = getDisplaysStates();
}
return ds;
}
private boolean areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount) {
if (displays.size() != expectedDisplayCount) {
return false;
}
for (ActivityDisplay display : displays) {
if (display.mOverrideConfiguration.densityDpi == 0) {
return false;
}
}
return true;
}
/** Checks if the device supports multi-display. */
protected boolean supportsMultiDisplay() {
return hasDeviceFeature(FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
}
}