Use ComponentName for VirtualDisplayService

This CL also moves DisplayHelper class to CTS APK as VirtualDisplayHelper,
and consolidates constants.

Bug: 73349193
Bug: 74043419
Test: No new test starts failing.
  atest CtsActivityManagerDeviceTestCases:ActivityManagerMultiDisplayTests
Change-Id: I4930715d3861dcc19ef2a97aeccc1a2fda965f10
diff --git a/tests/framework/base/activitymanager/Android.mk b/tests/framework/base/activitymanager/Android.mk
index 797610a..56db56d 100644
--- a/tests/framework/base/activitymanager/Android.mk
+++ b/tests/framework/base/activitymanager/Android.mk
@@ -32,13 +32,13 @@
     $(call all-named-files-under,Components.java, appPrereleaseSdk) \
     $(call all-named-files-under,Components.java, appSecondUid) \
     $(call all-named-files-under,Components.java, appThirdUid) \
+    $(call all-named-files-under,Components.java, displayserviceapp) \
     $(call all-named-files-under,Components.java, translucentapp) \
     $(call all-named-files-under,Components.java, translucentappsdk26) \
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
-    cts-amwm-util \
-    cts-display-service-app-util
+    cts-amwm-util
 
 LOCAL_CTS_TEST_PACKAGE := android.server
 
diff --git a/tests/framework/base/activitymanager/displayserviceapp/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/Android.mk
index b798d87..a844068 100644
--- a/tests/framework/base/activitymanager/displayserviceapp/Android.mk
+++ b/tests/framework/base/activitymanager/displayserviceapp/Android.mk
@@ -12,4 +12,24 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-include $(call all-subdir-makefiles)
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := cts-am-app-base
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+
+LOCAL_SDK_VERSION := test_current
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_PACKAGE_NAME := CtsDisplayServiceApp
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml b/tests/framework/base/activitymanager/displayserviceapp/AndroidManifest.xml
similarity index 79%
rename from tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml
rename to tests/framework/base/activitymanager/displayserviceapp/AndroidManifest.xml
index 4051804..8e9df70 100644
--- a/tests/framework/base/activitymanager/displayserviceapp/app/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/displayserviceapp/AndroidManifest.xml
@@ -14,13 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-          package="android.server.am.displayservice">
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    package="android.server.am.displayservice">
+
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
+
     <application android:label="CtsDisplayService">
-        <service android:name=".VirtualDisplayService"
-                 android:exported="true" />
+        <service
+            android:name=".VirtualDisplayService"
+            android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/framework/base/activitymanager/displayserviceapp/app/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/app/Android.mk
deleted file mode 100644
index c9cb7ef..0000000
--- a/tests/framework/base/activitymanager/displayserviceapp/app/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsDisplayServiceApp
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/displayserviceapp/src/android/server/am/displayservice/Components.java b/tests/framework/base/activitymanager/displayserviceapp/src/android/server/am/displayservice/Components.java
new file mode 100644
index 0000000..76d4d8f
--- /dev/null
+++ b/tests/framework/base/activitymanager/displayserviceapp/src/android/server/am/displayservice/Components.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.displayservice;
+
+import android.content.ComponentName;
+import android.server.am.component.ComponentsBase;
+
+public class Components extends ComponentsBase {
+
+    public static final ComponentName VIRTUAL_DISPLAY_SERVICE = component(
+            Components.class, "VirtualDisplayService");
+
+    /**
+     * Constants for {@link android.server.am.displayservice.VirtualDisplayService}.
+     */
+    public static final class VirtualDisplayService {
+        public static final String VIRTUAL_DISPLAY_NAME = "CtsVirtualDisplay";
+        // String extra key for command. Value should be one of COMMAND_* below.
+        public static final String EXTRA_COMMAND = "command";
+        // Boolean extra key to show keyguard on the display.
+        public static final String EXTRA_SHOW_CONTENT_WHEN_LOCKED = "show_content_when_locked";
+        // Extra values for {@link #EXTRA_COMMAND}.
+        public static final String COMMAND_CREATE = "create";
+        public static final String COMMAND_DESTROY = "destroy";
+        public static final String COMMAND_OFF = "off";
+        public static final String COMMAND_ON = "on";
+    }
+}
diff --git a/tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java b/tests/framework/base/activitymanager/displayserviceapp/src/android/server/am/displayservice/VirtualDisplayService.java
similarity index 60%
rename from tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java
rename to tests/framework/base/activitymanager/displayserviceapp/src/android/server/am/displayservice/VirtualDisplayService.java
index bf6b87e..e3aaefa 100644
--- a/tests/framework/base/activitymanager/displayserviceapp/app/src/android/server/am/displayservice/VirtualDisplayService.java
+++ b/tests/framework/base/activitymanager/displayserviceapp/src/android/server/am/displayservice/VirtualDisplayService.java
@@ -18,6 +18,15 @@
 
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.server.am.displayservice.Components.VirtualDisplayService.COMMAND_CREATE;
+import static android.server.am.displayservice.Components.VirtualDisplayService.COMMAND_DESTROY;
+import static android.server.am.displayservice.Components.VirtualDisplayService.COMMAND_OFF;
+import static android.server.am.displayservice.Components.VirtualDisplayService.COMMAND_ON;
+import static android.server.am.displayservice.Components.VirtualDisplayService.EXTRA_COMMAND;
+import static android.server.am.displayservice.Components.VirtualDisplayService
+        .EXTRA_SHOW_CONTENT_WHEN_LOCKED;
+import static android.server.am.displayservice.Components.VirtualDisplayService
+        .VIRTUAL_DISPLAY_NAME;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -28,15 +37,16 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
 import android.media.ImageReader;
-import android.os.Bundle;
 import android.os.IBinder;
 import android.util.Log;
-import android.view.Surface;
 
 public class VirtualDisplayService extends Service {
-    private static final String NOTIFICATION_CHANNEL_ID = "cts/VirtualDisplayService";
-    private static final String TAG = "VirtualDisplayService";
+    private static final String TAG = VirtualDisplayService.class.getSimpleName();
 
+    /** See {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD}. */
+    private static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5;
+
+    private static final String NOTIFICATION_CHANNEL_ID = "cts/VirtualDisplayService";
     private static final int FOREGROUND_ID = 1;
 
     private static final int DENSITY = 160;
@@ -52,8 +62,8 @@
 
         NotificationManager notificationManager = getSystemService(NotificationManager.class);
         notificationManager.createNotificationChannel(new NotificationChannel(
-            NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
-            NotificationManager.IMPORTANCE_DEFAULT));
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
         Notification notif = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
                 .setSmallIcon(android.R.drawable.ic_dialog_alert)
                 .build();
@@ -62,18 +72,23 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        String command = intent.getStringExtra("command");
+        String command = intent.getStringExtra(EXTRA_COMMAND);
         Log.d(TAG, "Got command: " + command);
 
-        if ("create".equals(command)) {
-            createVirtualDisplay(intent);
-        } if ("off".equals(command)) {
-            mVirtualDisplay.setSurface(null);
-        } else if ("on".equals(command)) {
-            mVirtualDisplay.setSurface(mReader.getSurface());
-        } else if ("destroy".equals(command)) {
-            destroyVirtualDisplay();
-            stopSelf();
+        switch (command) {
+            case COMMAND_CREATE:
+                createVirtualDisplay(intent);
+                break;
+            case COMMAND_OFF:
+                mVirtualDisplay.setSurface(null);
+                break;
+            case COMMAND_ON:
+                mVirtualDisplay.setSurface(mReader.getSurface());
+                break;
+            case COMMAND_DESTROY:
+                destroyVirtualDisplay();
+                stopSelf();
+                break;
         }
 
         return START_NOT_STICKY;
@@ -88,14 +103,13 @@
         mReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
 
         final DisplayManager displayManager = getSystemService(DisplayManager.class);
-        final String name = "CtsVirtualDisplay";
 
         int flags = VIRTUAL_DISPLAY_FLAG_PRESENTATION | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-        if (intent.getBooleanExtra("show_content_when_locked", false /* defaultValue */)) {
-            flags |= 1 << 5; // VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD
+        if (intent.getBooleanExtra(EXTRA_SHOW_CONTENT_WHEN_LOCKED, false /* defaultValue */)) {
+            flags |= VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
         }
         mVirtualDisplay = displayManager.createVirtualDisplay(
-                name, WIDTH, HEIGHT, DENSITY, mReader.getSurface(), flags);
+                VIRTUAL_DISPLAY_NAME, WIDTH, HEIGHT, DENSITY, mReader.getSurface(), flags);
     }
 
     private void destroyVirtualDisplay() {
diff --git a/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk b/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk
deleted file mode 100644
index 613888a..0000000
--- a/tests/framework/base/activitymanager/displayserviceapp/util/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    compatibility-device-util \
-    android-support-test
-
-LOCAL_MODULE := cts-display-service-app-util
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java b/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java
deleted file mode 100644
index f541770..0000000
--- a/tests/framework/base/activitymanager/displayserviceapp/util/src/android/server/am/displayservice/DisplayHelper.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.displayservice;
-
-import static junit.framework.Assert.assertTrue;
-
-import android.support.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class DisplayHelper {
-    private static final String VIRTUAL_DISPLAY_NAME = "CtsVirtualDisplay";
-    private static final String VIRTUAL_DISPLAY_SERVICE =
-            "android.server.am.displayservice/.VirtualDisplayService";
-    private static final Pattern mDisplayDevicePattern = Pattern.compile(
-            ".*DisplayDeviceInfo\\{\"([^\"]+)\":.*, state (\\S+),.*\\}.*");
-
-    private boolean mCreated;
-
-    public DisplayHelper() {
-    }
-
-    public void createAndWaitForDisplay(boolean external, boolean requestShowWhenLocked)
-            {
-        StringBuilder command =
-                new StringBuilder("am startfgservice -n " + VIRTUAL_DISPLAY_SERVICE);
-        command.append(" --es command create");
-        if (external) {
-            command.append(" --ez external_display true");
-        }
-        if (requestShowWhenLocked) {
-            command.append(" --ez show_content_when_locked true");
-        }
-        executeShellCommand(command.toString());
-
-        waitForDisplayState(false /* default */, true /* exists */, true /* on */);
-        mCreated = true;
-    }
-
-    public void turnDisplayOff() {
-        executeShellCommand(
-                "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command off");
-        waitForDisplayState(false /* default */, true /* exists */, false /* on */);
-    }
-
-    public void turnDisplayOn() {
-        executeShellCommand(
-                "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command on");
-        waitForDisplayState(false /* default */, true /* exists */, true /* on */);
-    }
-
-    public void releaseDisplay() {
-        if (mCreated) {
-            executeShellCommand(
-                    "am start-service -n " + VIRTUAL_DISPLAY_SERVICE + " --es command destroy");
-            waitForDisplayState(false /* default */, false /* exists */, true /* on */);
-        }
-        mCreated = false;
-    }
-
-    public static void waitForDefaultDisplayState(boolean wantOn) {
-        waitForDisplayState(true /* default */, true /* exists */, wantOn);
-    }
-
-    public static boolean getDefaultDisplayState() {
-        return getDisplayState(true);
-    }
-
-    private static void waitForDisplayState(boolean defaultDisplay, boolean wantExists, boolean wantOn) {
-        int tries = 0;
-        boolean done = false;
-        do {
-            if (tries > 0) {
-                try {
-                    Thread.sleep(500);
-                } catch (InterruptedException e) {
-                    // Oh well
-                }
-            }
-
-            Boolean state = getDisplayState(defaultDisplay);
-            done = (!wantExists && state == null)
-                    || (wantExists && state != null && state == wantOn);
-
-            tries++;
-        } while (tries < 10 && !done);
-
-        assertTrue(done);
-    }
-
-    private static Boolean getDisplayState(boolean defaultDisplay) {
-        String dump = executeShellCommand("dumpsys display");
-
-        boolean displayExists = false;
-        boolean displayOn = false;
-        for (String line : dump.split("\\n")) {
-            Matcher matcher = mDisplayDevicePattern.matcher(line);
-            if (matcher.matches()) {
-                if ((defaultDisplay && line.contains("FLAG_DEFAULT_DISPLAY"))
-                        || (!defaultDisplay && VIRTUAL_DISPLAY_NAME.equals(matcher.group(1)))) {
-                    return "ON".equals(matcher.group(2));
-                }
-            }
-        }
-        return null;
-    }
-
-    private static String executeShellCommand(String command) {
-        try {
-            return SystemUtil
-                    .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
-        } catch (IOException e) {
-            //bubble it up
-            throw new RuntimeException(e);
-        }
-    }
-}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
index 38deb3a..141cf4b 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
@@ -60,7 +60,6 @@
 import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
 import android.server.am.ActivityManagerState.ActivityDisplay;
-import android.server.am.displayservice.DisplayHelper;
 import android.support.annotation.Nullable;
 import android.support.test.filters.FlakyTest;
 
@@ -1725,7 +1724,7 @@
     private class ExternalDisplaySession implements AutoCloseable {
 
         @Nullable
-        private DisplayHelper mExternalDisplayHelper;
+        private VirtualDisplayHelper mExternalDisplayHelper;
 
         /**
          * Creates a private virtual display with the external and show with insecure
@@ -1736,9 +1735,8 @@
             final List<ActivityDisplay> originalDS = getDisplaysStates();
             final int originalDisplayCount = originalDS.size();
 
-            mExternalDisplayHelper = new DisplayHelper();
-            mExternalDisplayHelper.createAndWaitForDisplay(true /* external */,
-                    showContentWhenLocked);
+            mExternalDisplayHelper = new VirtualDisplayHelper();
+            mExternalDisplayHelper.createAndWaitForDisplay(showContentWhenLocked);
 
             // Wait for the virtual display to be created and get configurations.
             final List<ActivityDisplay> ds = getDisplayStateAfterChange(originalDisplayCount + 1);
@@ -1773,7 +1771,7 @@
         }
     }
 
-    private class PrimaryDisplayStateSession implements AutoCloseable {
+    private static class PrimaryDisplayStateSession implements AutoCloseable {
 
         void turnScreenOff() {
             setPrimaryDisplayState(false);
@@ -1791,7 +1789,7 @@
             } else {
                 pressSleepButton();
             }
-            DisplayHelper.waitForDefaultDisplayState(wantOn);
+            VirtualDisplayHelper.waitForDefaultDisplayState(wantOn);
         }
     }
 }
diff --git a/tests/framework/base/activitymanager/src/android/server/am/VirtualDisplayHelper.java b/tests/framework/base/activitymanager/src/android/server/am/VirtualDisplayHelper.java
new file mode 100644
index 0000000..3be9101
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/VirtualDisplayHelper.java
@@ -0,0 +1,133 @@
+/*
+ * 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.server.am.ComponentNameUtils.getActivityName;
+import static android.server.am.StateLogger.logAlways;
+import static android.server.am.displayservice.Components.VIRTUAL_DISPLAY_SERVICE;
+import static android.server.am.displayservice.Components.VirtualDisplayService.COMMAND_CREATE;
+import static android.server.am.displayservice.Components.VirtualDisplayService.COMMAND_DESTROY;
+import static android.server.am.displayservice.Components.VirtualDisplayService.COMMAND_OFF;
+import static android.server.am.displayservice.Components.VirtualDisplayService.COMMAND_ON;
+import static android.server.am.displayservice.Components.VirtualDisplayService.EXTRA_COMMAND;
+import static android.server.am.displayservice.Components.VirtualDisplayService.EXTRA_SHOW_CONTENT_WHEN_LOCKED;
+import static android.server.am.displayservice.Components.VirtualDisplayService.VIRTUAL_DISPLAY_NAME;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.fail;
+
+import android.os.SystemClock;
+import android.support.annotation.Nullable;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to create virtual display with communicating with VirtualDisplayService in
+ * CtsDisplayServiceApp.
+ */
+class VirtualDisplayHelper {
+    private static final Pattern DISPLAY_DEVICE_PATTERN = Pattern.compile(
+            ".*DisplayDeviceInfo\\{\"([^\"]+)\":.*, state (\\S+),.*\\}.*");
+
+    private boolean mCreated;
+
+    void createAndWaitForDisplay(boolean requestShowWhenLocked) {
+        StringBuilder command = new StringBuilder("am startfgservice -n "
+                + getActivityName(VIRTUAL_DISPLAY_SERVICE));
+        command.append(" --es " + EXTRA_COMMAND + " " + COMMAND_CREATE);
+        if (requestShowWhenLocked) {
+            command.append(" --ez " + EXTRA_SHOW_CONTENT_WHEN_LOCKED + " true");
+        }
+        executeShellCommand(command.toString());
+
+        waitForDisplayState(false /* default */, true /* on */);
+        mCreated = true;
+    }
+
+    void turnDisplayOff() {
+        executeShellCommand("am start-service -n " + getActivityName(VIRTUAL_DISPLAY_SERVICE)
+                + " --es " + EXTRA_COMMAND + " " + COMMAND_OFF);
+        waitForDisplayState(false /* default */, false /* on */);
+    }
+
+    void turnDisplayOn() {
+        executeShellCommand("am start-service -n " + getActivityName(VIRTUAL_DISPLAY_SERVICE)
+                        + " --es " + EXTRA_COMMAND + " " + COMMAND_ON);
+        waitForDisplayState(false /* default */, true /* on */);
+    }
+
+    void releaseDisplay() {
+        if (mCreated) {
+            executeShellCommand("am start-service -n " + getActivityName(VIRTUAL_DISPLAY_SERVICE)
+                            + " --es " + EXTRA_COMMAND + " " + COMMAND_DESTROY);
+            waitForDisplayCondition(false /* defaultDisplay */, Objects::isNull,
+                    "Waiting for virtual display destroy");
+        }
+        mCreated = false;
+    }
+
+    static void waitForDefaultDisplayState(boolean wantOn) {
+        waitForDisplayState(true /* default */, wantOn);
+    }
+
+    private static void waitForDisplayState(boolean defaultDisplay, boolean wantOn) {
+        waitForDisplayCondition(defaultDisplay, state -> state != null && state == wantOn,
+                "Waiting for " + (defaultDisplay ? "default" : "virtual") + " display "
+                        + (wantOn ? "on" : "off"));
+    }
+
+    private static void waitForDisplayCondition(boolean defaultDisplay,
+            Predicate<Boolean> condition, String message) {
+        for (int retry = 1; retry <= 10; retry++) {
+            if (condition.test(getDisplayState(defaultDisplay))) {
+                return;
+            }
+            logAlways(message + "... retry=" + retry);
+            SystemClock.sleep(500);
+        }
+        fail(message + " failed");
+    }
+
+    @Nullable
+    private static Boolean getDisplayState(boolean defaultDisplay) {
+        final String dump = executeShellCommand("dumpsys display");
+        final Predicate<Matcher> displayNameMatcher = defaultDisplay
+                ? m -> m.group(0).contains("FLAG_DEFAULT_DISPLAY")
+                : m -> m.group(1).equals(VIRTUAL_DISPLAY_NAME);
+        for (final String line : dump.split("\\n")) {
+            final Matcher matcher = DISPLAY_DEVICE_PATTERN.matcher(line);
+            if (matcher.matches() && displayNameMatcher.test(matcher)) {
+                return "ON".equals(matcher.group(2));
+            }
+        }
+        return null;
+    }
+
+    private static String executeShellCommand(String command) {
+        try {
+            return SystemUtil.runShellCommand(getInstrumentation(), command);
+        } catch (IOException e) {
+            //bubble it up
+            throw new RuntimeException(e);
+        }
+    }
+}