| /* |
| * 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.cts; |
| |
| import com.android.tradefed.device.CollectingOutputReceiver; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| |
| import static android.server.cts.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID; |
| import static android.server.cts.StateLogger.log; |
| import static android.server.cts.StateLogger.logE; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| 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 String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity processes"; |
| private static final String DUMPSYS_DISPLAY = "dumpsys display"; |
| private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity"; |
| private static final int INVALID_DENSITY_DPI = -1; |
| |
| private boolean mVirtualDisplayCreated; |
| |
| /** Temp storage used for parsing. */ |
| final LinkedList<String> mDumpLines = new LinkedList<>(); |
| final LinkedList<String> mAltDumpLines = new LinkedList<>(); |
| |
| @Override |
| protected void tearDown() throws Exception { |
| try { |
| destroyVirtualDisplays(); |
| } catch (DeviceNotAvailableException e) { |
| logE(e.getMessage()); |
| } |
| super.tearDown(); |
| } |
| |
| /** Contains the configurations applied to attached displays. */ |
| static final class DisplayState { |
| int mDisplayId; |
| String mOverrideConfig; |
| String mDisplayViewportId; |
| |
| private DisplayState(int displayId, String overrideConfig, String uniqueId) { |
| mDisplayId = displayId; |
| mOverrideConfig = overrideConfig; |
| mDisplayViewportId = uniqueId; |
| } |
| |
| private int getWidth() { |
| final String[] configParts = mOverrideConfig.split(" "); |
| for (String part : configParts) { |
| if (part.endsWith("dp") && part.startsWith("w")) { |
| final String widthString = part.substring(1, part.length() - 3); |
| return Integer.parseInt(widthString); |
| } |
| } |
| |
| return -1; |
| } |
| |
| private int getHeight() { |
| final String[] configParts = mOverrideConfig.split(" "); |
| for (String part : configParts) { |
| if (part.endsWith("dp") && part.startsWith("h")) { |
| final String heightString = part.substring(1, part.length() - 3); |
| return Integer.parseInt(heightString); |
| } |
| } |
| |
| return -1; |
| } |
| |
| int getDpi() { |
| final String[] configParts = mOverrideConfig.split(" "); |
| for (String part : configParts) { |
| if (part.endsWith("dpi")) { |
| final String densityDpiString = part.substring(0, part.length() - 3); |
| return Integer.parseInt(densityDpiString); |
| } |
| } |
| |
| return -1; |
| } |
| |
| String getDisplayViewPortId() { |
| return mDisplayViewportId; |
| } |
| } |
| |
| /** Contains the configurations applied to attached displays. */ |
| static final class ReportedDisplays { |
| private static final Pattern sGlobalConfigurationPattern = |
| Pattern.compile("mGlobalConfiguration: (\\{.*\\})"); |
| private static final Pattern sDisplayOverrideConfigurationsPattern = |
| Pattern.compile("Display override configurations:"); |
| private static final Pattern sDisplayConfigPattern = |
| Pattern.compile("(\\d+): (\\{.*\\})"); |
| private static final Pattern sVirtualTouchViewportPattern = |
| Pattern.compile("mVirtualTouchViewports.*displayId=(\\d+), uniqueId='([^']+)', .*"); |
| |
| String mGlobalConfig; |
| private Map<Integer, DisplayState> mDisplayStates = new HashMap<>(); |
| |
| static ReportedDisplays create(LinkedList<String> activityProcessDump, |
| LinkedList<String> displayDump) { |
| final ReportedDisplays result = new ReportedDisplays(); |
| HashMap<String, String> virtualUniqueIdMap = new HashMap<String, String>(); |
| |
| while (!displayDump.isEmpty()) { |
| final String line = displayDump.pop().trim(); |
| |
| Matcher matcher = sVirtualTouchViewportPattern.matcher(line); |
| if (matcher.matches()) { |
| virtualUniqueIdMap.put(matcher.group(1), matcher.group(2)); |
| } |
| } |
| |
| while (!activityProcessDump.isEmpty()) { |
| final String line = activityProcessDump.pop().trim(); |
| |
| |
| Matcher actMatcher = sDisplayOverrideConfigurationsPattern.matcher(line); |
| if (actMatcher.matches()) { |
| log(line); |
| while (ReportedDisplays.shouldContinueExtracting(activityProcessDump, |
| sDisplayConfigPattern)) { |
| final String displayOverrideConfigLine = activityProcessDump.pop().trim(); |
| log(displayOverrideConfigLine); |
| actMatcher = sDisplayConfigPattern.matcher(displayOverrideConfigLine); |
| actMatcher.matches(); |
| final String tempUniqueId = "local:" + actMatcher.group(1); |
| final Integer displayId = Integer.valueOf(actMatcher.group(1)); |
| // Default unique ids for non virtual display. |
| final String uniqueId = |
| (virtualUniqueIdMap.containsKey(actMatcher.group(1))) |
| ? virtualUniqueIdMap.get(actMatcher.group(1)) : tempUniqueId; |
| result.mDisplayStates.put(displayId, |
| new DisplayState(displayId, actMatcher.group(2), uniqueId)); |
| } |
| continue; |
| } |
| |
| actMatcher = sGlobalConfigurationPattern.matcher(line); |
| if (actMatcher.matches()) { |
| log(line); |
| result.mGlobalConfig = actMatcher.group(1); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** Check if next line in dump matches the pattern and we should continue extracting. */ |
| static boolean shouldContinueExtracting(LinkedList<String> dump, Pattern matchingPattern) { |
| if (dump.isEmpty()) { |
| return false; |
| } |
| |
| final String line = dump.peek().trim(); |
| return matchingPattern.matcher(line).matches(); |
| } |
| |
| DisplayState getDisplayState(int displayId) { |
| return mDisplayStates.get(displayId); |
| } |
| |
| /** Return the display state with width, height, dpi */ |
| DisplayState getDisplayState(int width, int height, int dpi) { |
| for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) { |
| final DisplayState ds = entry.getValue(); |
| if (ds.mDisplayId != DEFAULT_DISPLAY_ID && ds.getDpi() == dpi |
| && ds.getWidth() == width && ds.getHeight() == height) { |
| return ds; |
| } |
| } |
| return null; |
| } |
| |
| /** Return the display state with the given unique id */ |
| DisplayState getDisplayState(String viewportId) { |
| for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) { |
| final DisplayState ds = entry.getValue(); |
| if (ds.mDisplayViewportId.equals(viewportId)) { |
| return ds; |
| } |
| } |
| return null; |
| } |
| |
| /** Check if reported state is valid. */ |
| boolean isValidState(int expectedDisplayCount) { |
| if (mDisplayStates.size() != expectedDisplayCount) { |
| return false; |
| } |
| |
| for (Map.Entry<Integer, DisplayState> entry : mDisplayStates.entrySet()) { |
| final DisplayState ds = entry.getValue(); |
| if (ds.mDisplayId != DEFAULT_DISPLAY_ID && ds.getDpi() == -1) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| ReportedDisplays getDisplaysStates() throws DeviceNotAvailableException { |
| // Parse dumpsys activity processes. |
| final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); |
| mDevice.executeShellCommand(DUMPSYS_ACTIVITY_PROCESSES, outputReceiver); |
| String dump = outputReceiver.getOutput(); |
| mDumpLines.clear(); |
| |
| Collections.addAll(mDumpLines, dump.split("\\n")); |
| |
| // Parse dumpsys display |
| final CollectingOutputReceiver outputReceiverNew = new CollectingOutputReceiver(); |
| mDevice.executeShellCommand(DUMPSYS_DISPLAY, outputReceiverNew); |
| String dumpNew = outputReceiverNew.getOutput(); |
| mAltDumpLines.clear(); |
| |
| Collections.addAll(mAltDumpLines, dumpNew.split("\\n")); |
| |
| return ReportedDisplays.create(mDumpLines, mAltDumpLines); |
| } |
| |
| /** Find the display that was not originally reported in oldDisplays and added in newDisplays */ |
| private List<ActivityManagerDisplayTests.DisplayState> findNewDisplayStates( |
| ReportedDisplays oldDisplays, ReportedDisplays newDisplays) { |
| final ArrayList<DisplayState> displays = new ArrayList(); |
| |
| for (Integer displayId : newDisplays.mDisplayStates.keySet()) { |
| if (!oldDisplays.mDisplayStates.containsKey(displayId)) { |
| displays.add(newDisplays.getDisplayState(displayId)); |
| } |
| } |
| |
| return displays; |
| } |
| |
| /** |
| * Create new virtual display. |
| * @param densityDpi provide custom density for the display. |
| * @param launchInSplitScreen start {@link VirtualDisplayActivity} to side from |
| * {@link LaunchingActivity} on primary display. |
| * @param publicDisplay make display public. |
| * @param resizeDisplay should resize display when surface size changes. |
| * @param launchActivity should launch test activity immediately after display creation. |
| * @return {@link ActivityManagerDisplayTests.DisplayState} of newly created display. |
| * @throws Exception |
| */ |
| private List<ActivityManagerDisplayTests.DisplayState> createVirtualDisplays(int densityDpi, |
| boolean launchInSplitScreen, boolean publicDisplay, boolean resizeDisplay, |
| String launchActivity, int displayCount) throws Exception { |
| // Start an activity that is able to create virtual displays. |
| if (launchInSplitScreen) { |
| getLaunchActivityBuilder().setToSide(true) |
| .setTargetActivityName(VIRTUAL_DISPLAY_ACTIVITY).execute(); |
| } else { |
| launchActivity(VIRTUAL_DISPLAY_ACTIVITY); |
| } |
| mAmWmState.computeState(mDevice, new String[] {VIRTUAL_DISPLAY_ACTIVITY}, |
| false /* compareTaskAndStackBounds */); |
| final ActivityManagerDisplayTests.ReportedDisplays originalDS = getDisplaysStates(); |
| final int originalDisplayCount = originalDS.mDisplayStates.size(); |
| |
| // Create virtual display with custom density dpi. |
| executeShellCommand(getCreateVirtualDisplayCommand(densityDpi, publicDisplay, resizeDisplay, |
| launchActivity, displayCount)); |
| mVirtualDisplayCreated = true; |
| |
| // Wait for the virtual display to be created and get configurations. |
| final ActivityManagerDisplayTests.ReportedDisplays ds = |
| getDisplayStateAfterChange(originalDisplayCount + displayCount); |
| assertEquals("New virtual display must be created", |
| originalDisplayCount + displayCount, ds.mDisplayStates.size()); |
| |
| // Find the newly added display. |
| final List<ActivityManagerDisplayTests.DisplayState> newDisplays |
| = findNewDisplayStates(originalDS, ds); |
| assertTrue("New virtual display must be created", displayCount == newDisplays.size()); |
| |
| return newDisplays; |
| } |
| |
| /** |
| * Destroy existing virtual display. |
| */ |
| void destroyVirtualDisplays() throws Exception { |
| if (mVirtualDisplayCreated) { |
| executeShellCommand(getDestroyVirtualDisplayCommand()); |
| mVirtualDisplayCreated = false; |
| } |
| } |
| |
| static class VirtualDisplayBuilder { |
| private final ActivityManagerDisplayTestBase mTests; |
| |
| private int mDensityDpi = CUSTOM_DENSITY_DPI; |
| private boolean mLaunchInSplitScreen = false; |
| private boolean mPublicDisplay = false; |
| private boolean mResizeDisplay = true; |
| private String mLaunchActivity = null; |
| |
| public VirtualDisplayBuilder(ActivityManagerDisplayTestBase tests) { |
| mTests = tests; |
| } |
| |
| public VirtualDisplayBuilder setDensityDpi(int densityDpi) { |
| mDensityDpi = densityDpi; |
| return this; |
| } |
| |
| public VirtualDisplayBuilder setLaunchInSplitScreen(boolean launchInSplitScreen) { |
| mLaunchInSplitScreen = launchInSplitScreen; |
| return this; |
| } |
| |
| public VirtualDisplayBuilder setPublicDisplay(boolean publicDisplay) { |
| mPublicDisplay = publicDisplay; |
| return this; |
| } |
| |
| public VirtualDisplayBuilder setResizeDisplay(boolean resizeDisplay) { |
| mResizeDisplay = resizeDisplay; |
| return this; |
| } |
| |
| public VirtualDisplayBuilder setLaunchActivity(String launchActivity) { |
| mLaunchActivity = launchActivity; |
| return this; |
| } |
| |
| public DisplayState build() throws Exception { |
| final List<DisplayState> displays = build(1); |
| return displays != null && !displays.isEmpty() ? displays.get(0) : null; |
| } |
| |
| public List<DisplayState> build(int count) throws Exception { |
| return mTests.createVirtualDisplays(mDensityDpi, mLaunchInSplitScreen, mPublicDisplay, |
| mResizeDisplay, mLaunchActivity, count); |
| } |
| } |
| |
| private static String getCreateVirtualDisplayCommand(int densityDpi, boolean publicDisplay, |
| boolean resizeDisplay, String launchActivity, int displayCount) { |
| final StringBuilder commandBuilder |
| = new StringBuilder(getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY)); |
| commandBuilder.append(" -f 0x20000000"); |
| commandBuilder.append(" --es command create_display"); |
| if (densityDpi != INVALID_DENSITY_DPI) { |
| commandBuilder.append(" --ei density_dpi ").append(densityDpi); |
| } |
| commandBuilder.append(" --ei count ").append(displayCount); |
| commandBuilder.append(" --ez public_display ").append(publicDisplay); |
| commandBuilder.append(" --ez resize_display ").append(resizeDisplay); |
| if (launchActivity != null) { |
| commandBuilder.append(" --es launch_target_activity ").append(launchActivity); |
| } |
| return commandBuilder.toString(); |
| } |
| |
| private static String getDestroyVirtualDisplayCommand() { |
| return getAmStartCmd(VIRTUAL_DISPLAY_ACTIVITY) + " -f 0x20000000" + |
| " --es command destroy_display"; |
| } |
| |
| /** Wait for provided number of displays and report their configurations. */ |
| ReportedDisplays getDisplayStateAfterChange(int expectedDisplayCount) |
| throws DeviceNotAvailableException { |
| ReportedDisplays ds = getDisplaysStates(); |
| |
| int retriesLeft = 5; |
| while (!ds.isValidState(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; |
| } |
| |
| /** Checks if the device supports multi-display. */ |
| boolean supportsMultiDisplay() throws Exception { |
| return hasDeviceFeature("android.software.activities_on_secondary_displays"); |
| } |
| } |