cts: test replacing window functionality

bug: 26324082
bug: 19225708

Change-Id: Ia4e89d45ce3e260de65bd7b8b25049e6828c81fb
diff --git a/hostsidetests/services/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
index 995649f..7e1662d 100755
--- a/hostsidetests/services/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
@@ -24,6 +24,15 @@
                 android:resizeable="true"
                 android:exported="true"
         />
+        <activity android:name=".NoRelaunchActivity"
+                android:resizeable="true"
+                android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection"
+                android:exported="true"
+        />
+        <activity android:name=".SlowCreateActivity"
+                android:resizeable="true"
+                android:exported="true"
+        />
         <activity android:name=".LaunchToSideActivity"
                 android:resizeable="true"
                 android:exported="true"
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/NoRelaunchActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/NoRelaunchActivity.java
new file mode 100644
index 0000000..37963de
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/NoRelaunchActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+import android.app.Activity;
+
+public class NoRelaunchActivity extends Activity {
+}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/SlowCreateActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/SlowCreateActivity.java
new file mode 100644
index 0000000..481f5be
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/SlowCreateActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SlowCreateActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        try {
+            Thread.sleep(2000);
+        } catch(InterruptedException e) {}
+        super.onCreate(savedInstanceState);
+    }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityAndWindowManagersState.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityAndWindowManagersState.java
index 5926698..fc60b78 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityAndWindowManagersState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityAndWindowManagersState.java
@@ -29,8 +29,20 @@
     private WindowManagerState mWmState = new WindowManagerState();
 
     void computeState(ITestDevice device) throws Exception {
+        computeState(device, true);
+    }
+
+    void computeState(ITestDevice device, boolean visibleOnly) throws Exception {
         mAmState.computeState(device);
-        mWmState.computeState(device);
+        mWmState.computeState(device, visibleOnly);
+    }
+
+    ActivityManagerState getAmState() {
+        return mAmState;
+    }
+
+    WindowManagerState getWmState() {
+        return mWmState;
     }
 
     void assertSanity() throws Exception {
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
new file mode 100644
index 0000000..a4ec55b
--- /dev/null
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerReplaceWindowTests.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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 java.lang.Exception;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import junit.framework.Assert;
+
+import static com.android.ddmlib.Log.LogLevel.INFO;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public class ActivityManagerReplaceWindowTests extends ActivityManagerTestBase {
+
+    private static final String SLOW_CREATE_ACTIVITY_NAME = "SlowCreateActivity";
+    private static final String NO_RELAUNCH_ACTIVITY_NAME = "NoRelaunchActivity";
+
+    private static final String AM_FORCE_STOP_TEST = "am force-stop android.server.app";
+    private static final String AM_MOVE_TASK = "am stack movetask ";
+
+    private List<String> mTempWindowTokens = new ArrayList();
+
+    @Override
+    protected void tearDown() {
+        try {
+            mDevice.executeShellCommand(AM_FORCE_STOP_TEST);
+        } catch (DeviceNotAvailableException e) {
+        }
+    }
+
+    public void testReplaceWindow_Dock_Relaunch() throws Exception {
+        testReplaceWindow_Dock(true);
+    }
+
+    public void testReplaceWindow_Dock_NoRelaunch() throws Exception {
+        testReplaceWindow_Dock(false);
+    }
+
+    private void testReplaceWindow_Dock(boolean relaunch) throws Exception {
+        final String activityName =
+                relaunch ? SLOW_CREATE_ACTIVITY_NAME : NO_RELAUNCH_ACTIVITY_NAME;
+        final String windowName = getWindowName(activityName);
+        final String amStartCmd = getAmStartCmd(activityName);
+
+        mDevice.executeShellCommand(amStartCmd);
+
+        // Sleep 2 seconds, then check if the window is started properly.
+        // SlowCreateActivity will do a sleep inside its onCreate() to simulate a
+        // slow-starting app. So instead of relying on WindowManagerState's
+        // retrying mechanism, we do an explicit sleep to avoid excess spews
+        // from WindowManagerState.
+        if (SLOW_CREATE_ACTIVITY_NAME.equals(activityName)) {
+            Thread.sleep(2000);
+        }
+
+        CLog.logAndDisplay(INFO, "==========Before Docking========");
+        final String oldToken = getFocusedWindowToken(windowName, true);
+
+        // Move to docked stack
+        final int taskId = getActivityTaskId(activityName);
+        final String cmd = AM_MOVE_TASK + taskId + " " + DOCKED_STACK_ID + " true";
+        mDevice.executeShellCommand(cmd);
+
+        // Sleep 5 seconds, then check if the window is replaced properly.
+        Thread.sleep(5000);
+
+        CLog.logAndDisplay(INFO, "==========After Docking========");
+        final String newToken = getFocusedWindowToken(windowName, false);
+
+        if (relaunch) {
+            Assert.assertFalse("Window not replaced after relaunch.", oldToken.equals(newToken));
+        } else {
+            Assert.assertEquals("Window replaced without relaunch.", oldToken, newToken);
+        }
+    }
+
+    private String getFocusedWindowToken(String windowName, boolean visibleOnly)
+            throws Exception {
+        mAmWmState.computeState(mDevice, visibleOnly);
+
+        mAmWmState.assertSanity();
+
+        mAmWmState.assertFocusedWindow("Test window must be the front window.",
+                windowName);
+
+        mAmWmState.getWmState().getMatchingWindowTokens(windowName, mTempWindowTokens);
+
+        Assert.assertEquals("Should have exactly one window for the activity.",
+                1, mTempWindowTokens.size());
+
+        return mTempWindowTokens.get(0);
+    }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index 65c375e..953d185 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -59,6 +59,14 @@
 
     private HashSet<String> mAvailableFeatures;
 
+    protected static String getAmStartCmd(final String activityName) {
+        return "am start -n android.server.app/." + activityName;
+    }
+
+    protected static String getWindowName(final String activityName) {
+        return "android.server.app/android.server.app." + activityName;
+    }
+
     protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState();
 
     @Override
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
index 097b659..169e245 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
@@ -34,14 +34,15 @@
 import static com.android.ddmlib.Log.LogLevel.INFO;
 
 class WindowManagerState {
+    private static final String DUMPSYS_WINDOWS_APPS = "dumpsys window apps";
     private static final String DUMPSYS_WINDOWS_VISIBLE_APPS = "dumpsys window visible-apps";
 
     private final Pattern mWindowPattern =
-            Pattern.compile("Window #(\\d+) Window\\{(.+) u(\\d+) (.+)\\}\\:");
+            Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+)\\}\\:");
     private final Pattern mStartingWindowPattern =
-            Pattern.compile("Window #(\\d+) Window\\{(.+) u(\\d+) Starting (.+)\\}\\:");
+            Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) Starting (.+)\\}\\:");
     private final Pattern mExitingWindowPattern =
-            Pattern.compile("Window #(\\d+) Window\\{(.+) u(\\d+) (.+) EXITING\\}\\:");
+            Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+) EXITING\\}\\:");
 
     private final Pattern mFocusedWindowPattern =
             Pattern.compile("mCurrentFocus=Window\\{(.+) u(\\d+) (\\S+)\\}");
@@ -57,12 +58,13 @@
 
     // Windows in z-order with the top most at the front of the list.
     private List<String> mWindows = new ArrayList();
+    private List<String> mRawWindows = new ArrayList();
     private List<WindowStack> mStacks = new ArrayList();
     private String mFocusedWindow = null;
     private String mFocusedApp = null;
     private final LinkedList<String> mSysDump = new LinkedList();
 
-    void computeState(ITestDevice device) throws DeviceNotAvailableException {
+    void computeState(ITestDevice device, boolean visibleOnly) throws DeviceNotAvailableException {
         // It is possible the system is in the middle of transition to the right state when we get
         // the dump. We try a few times to get the information we need before giving up.
         int retriesLeft = 3;
@@ -82,9 +84,11 @@
             }
 
             final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
-            device.executeShellCommand(DUMPSYS_WINDOWS_VISIBLE_APPS, outputReceiver);
+            final String dumpsysCmd = visibleOnly ?
+                    DUMPSYS_WINDOWS_VISIBLE_APPS : DUMPSYS_WINDOWS_APPS;
+            device.executeShellCommand(dumpsysCmd, outputReceiver);
             dump = outputReceiver.getOutput();
-            parseSysDump(dump);
+            parseSysDump(dump, visibleOnly);
 
             retry = mWindows.isEmpty() || mFocusedWindow == null || mFocusedApp == null;
         } while (retry && retriesLeft-- > 0);
@@ -104,7 +108,7 @@
         }
     }
 
-    private void parseSysDump(String sysDump) {
+    private void parseSysDump(String sysDump,boolean visibleOnly) {
         reset();
 
         Collections.addAll(mSysDump, sysDump.split("\\n"));
@@ -125,7 +129,7 @@
                 CLog.logAndDisplay(INFO, line);
                 final String window = matcher.group(4);
 
-                if (mWindows.isEmpty()) {
+                if (visibleOnly && mWindows.isEmpty()) {
                     // This is the front window. Check to see if we are in the middle of
                     // transitioning. If we are, we want to skip dumping until window manager is
                     // done transitioning the top window.
@@ -146,6 +150,7 @@
 
                 CLog.logAndDisplay(INFO, window);
                 mWindows.add(window);
+                mRawWindows.add(line);
                 continue;
             }
 
@@ -169,6 +174,20 @@
         }
     }
 
+    void getMatchingWindowTokens(final String windowName, List<String> tokenList) {
+        tokenList.clear();
+
+        for (String line : mRawWindows) {
+            if (line.contains(windowName)) {
+                Matcher matcher = mWindowPattern.matcher(line);
+                if (matcher.matches()) {
+                    CLog.logAndDisplay(INFO, "Found activity window: " + line);
+                    tokenList.add(matcher.group(2));
+                }
+            }
+        }
+    }
+
     String getFrontWindow() {
         return mWindows.get(0);
     }
@@ -195,8 +214,10 @@
     }
 
     private void reset() {
+        mSysDump.clear();
         mStacks.clear();
         mWindows.clear();
+        mRawWindows.clear();
         mFocusedWindow = null;
         mFocusedApp = null;
     }