cts: add test for AndroidManifestLayout

bug: 27157489
Change-Id: Ica7f6dba42cc86f7819f2d4eb13174567be4cdc5
diff --git a/hostsidetests/services/activitymanager/app/AndroidManifest.xml b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
index ff861c0..8a2a448 100755
--- a/hostsidetests/services/activitymanager/app/AndroidManifest.xml
+++ b/hostsidetests/services/activitymanager/app/AndroidManifest.xml
@@ -89,6 +89,46 @@
                   android:taskAffinity="nobody.but.FreeformActivity"
                   android:exported="true"
         />
+        <activity android:name=".TopLeftLayoutActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true">
+                  <layout android:defaultWidth="160dp"
+                          android:defaultHeight="160dp"
+                          android:gravity="top|left"
+                          android:minimalSize="80dp"
+                  />
+        </activity>
+        <activity android:name=".TopRightLayoutActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true">
+                  <layout android:defaultWidth="25%"
+                          android:defaultHeight="25%"
+                          android:gravity="top|right"
+                          android:minimalSize="80dp"
+                  />
+        </activity>
+        <activity android:name=".BottomLeftLayoutActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true">
+                  <layout android:defaultWidth="25%"
+                          android:defaultHeight="25%"
+                          android:gravity="bottom|left"
+                          android:minimalSize="80dp"
+                  />
+        </activity>
+        <activity android:name=".BottomRightLayoutActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+                  android:exported="true">
+                  <layout android:defaultWidth="160dp"
+                          android:defaultHeight="160dp"
+                          android:gravity="bottom|right"
+                          android:minimalSize="80dp"
+                  />
+        </activity>
     </application>
 </manifest>
 
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/BottomLeftLayoutActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/BottomLeftLayoutActivity.java
new file mode 100644
index 0000000..27a6fe2
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/BottomLeftLayoutActivity.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 BottomLeftLayoutActivity extends Activity {
+}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/BottomRightLayoutActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/BottomRightLayoutActivity.java
new file mode 100644
index 0000000..7a91510
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/BottomRightLayoutActivity.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 BottomRightLayoutActivity extends Activity {
+}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/TopLeftLayoutActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/TopLeftLayoutActivity.java
new file mode 100644
index 0000000..3a0267b
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/TopLeftLayoutActivity.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 TopLeftLayoutActivity extends Activity {
+}
diff --git a/hostsidetests/services/activitymanager/app/src/android/server/app/TopRightLayoutActivity.java b/hostsidetests/services/activitymanager/app/src/android/server/app/TopRightLayoutActivity.java
new file mode 100644
index 0000000..eb3b1b0
--- /dev/null
+++ b/hostsidetests/services/activitymanager/app/src/android/server/app/TopRightLayoutActivity.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 TopRightLayoutActivity extends Activity {
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
index e933d26..bc48964 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerFreeformStackTests.java
@@ -75,12 +75,4 @@
         assertActivityLifecycle(TEST_ACTIVITY, true);
         assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false);
     }
-
-    private void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
-            throws Exception {
-        final int taskId = getActivityTaskId(activityName);
-        final String cmd = "am task resize "
-                + taskId + " " + left + " " + top + " " + right + " " + bottom;
-        mDevice.executeShellCommand(cmd);
-    }
 }
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
new file mode 100644
index 0000000..78cc4b9
--- /dev/null
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerManifestLayoutTests.java
@@ -0,0 +1,172 @@
+/*
+ * 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 java.awt.Rectangle;
+import android.server.cts.WindowManagerState.WindowState;
+import android.server.cts.WindowManagerState.Display;
+
+import static com.android.ddmlib.Log.LogLevel.INFO;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public class ActivityManagerManifestLayoutTests extends ActivityManagerTestBase {
+
+    // Clone of android DisplayMetrics.DENSITY_DEFAULT (DENSITY_MEDIUM)
+    // (Needed in host-side test to convert dp to px.)
+    private static final int DISPLAY_DENSITY_DEFAULT = 160;
+
+    // Test parameters
+    private static final int DEFAULT_WIDTH_DP = 160;
+    private static final int DEFAULT_HEIGHT_DP = 160;
+    private static final float DEFAULT_WIDTH_FRACTION = 0.25f;
+    private static final float DEFAULT_HEIGHT_FRACTION = 0.25f;
+    private static final int MINIMAL_SIZE_DP = 80;
+
+    private static final int GRAVITY_VER_CENTER = 0x01;
+    private static final int GRAVITY_VER_TOP    = 0x02;
+    private static final int GRAVITY_VER_BOTTOM = 0x04;
+    private static final int GRAVITY_HOR_CENTER = 0x10;
+    private static final int GRAVITY_HOR_LEFT   = 0x20;
+    private static final int GRAVITY_HOR_RIGHT  = 0x40;
+
+    private List<WindowState> mTempWindowList = new ArrayList();
+
+    public void testGravityAndDefaultSizeTopLeft() throws Exception {
+        testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_LEFT, false /*fraction*/, false /*minimize*/);
+    }
+
+    public void testGravityAndDefaultSizeTopRight() throws Exception {
+        testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_RIGHT, true /*fraction*/, false /*minimize*/);
+    }
+
+    public void testGravityAndDefaultSizeBottomLeft() throws Exception {
+        testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_LEFT, true /*fraction*/, false /*minimize*/);
+    }
+
+    public void testGravityAndDefaultSizeBottomRight() throws Exception {
+        testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_RIGHT, false /*fraction*/, false /*minimize*/);
+    }
+
+    public void testMinimalSize() throws Exception {
+        testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_LEFT, false /*fraction*/, true /*minimize*/);
+    }
+
+    private void testLayout(
+            int vGravity, int hGravity, boolean fraction, boolean minimize) throws Exception {
+        if (!supportsFreeform()) {
+            CLog.logAndDisplay(INFO, "Skipping test: no freeform support");
+            return;
+        }
+
+        final String activityName = (vGravity == GRAVITY_VER_TOP ? "Top" : "Bottom")
+                + (hGravity == GRAVITY_HOR_LEFT ? "Left" : "Right") + "LayoutActivity";
+
+        // Launch in freeform stack
+        launchActivityInStack(activityName, FREEFORM_WORKSPACE_STACK_ID);
+
+        int expectedWidthDp = DEFAULT_WIDTH_DP;
+        int expectedHeightDp = DEFAULT_HEIGHT_DP;
+
+        // If we're testing fraction dimensions, set the expected to -1. The expected value
+        // depends on the display size, and will be evaluated when we have display info.
+        if (fraction) {
+            expectedWidthDp = expectedHeightDp = -1;
+        }
+
+        // If we're testing minimal size, issue command to resize to <0,0,1,1>. We expect
+        // the size to be floored at MINIMAL_SIZE_DPxMINIMAL_SIZE_DP.
+        if (minimize) {
+            resizeActivityTask(activityName, 0, 0, 1, 1);
+            expectedWidthDp = expectedHeightDp = MINIMAL_SIZE_DP;
+        }
+
+        verifyWindowState(activityName, vGravity, hGravity, expectedWidthDp, expectedHeightDp);
+    }
+
+    private void verifyWindowState(String activityName, int vGravity, int hGravity,
+            int expectedWidthDp, int expectedHeightDp) throws Exception {
+        final String windowName = getWindowName(activityName);
+
+        mAmWmState.computeState(mDevice, true /* visibleOnly */, new String[] {activityName});
+
+        mAmWmState.assertSanity();
+
+        mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
+
+        mAmWmState.getWmState().getMatchingWindowState(windowName, mTempWindowList);
+
+        Assert.assertEquals("Should have exactly one window state for the activity.",
+                1, mTempWindowList.size());
+
+        WindowState ws = mTempWindowList.get(0);
+
+        Display display = mAmWmState.getWmState().getDisplay(ws.getDisplayId());
+        Assert.assertNotNull("Should be on a display", display);
+
+        final Rectangle containingRect = ws.getContainingFrame();
+        final Rectangle appRect = display.getAppRect();
+        final int expectedWidthPx, expectedHeightPx;
+        // Evaluate the expected window size in px. If we're using fraction dimensions,
+        // calculate the size based on the app rect size. Otherwise, convert the expected
+        // size in dp to px.
+        if (expectedWidthDp < 0 || expectedHeightDp < 0) {
+            expectedWidthPx = (int) (appRect.width * DEFAULT_WIDTH_FRACTION);
+            expectedHeightPx = (int) (appRect.height * DEFAULT_HEIGHT_FRACTION);
+        } else {
+            final int densityDpi = display.getDpi();
+            expectedWidthPx = dpToPx(expectedWidthDp, densityDpi);
+            expectedHeightPx = dpToPx(expectedHeightDp, densityDpi);
+        }
+        verifyFrameSizeAndPosition(
+                vGravity, hGravity, expectedWidthPx, expectedHeightPx, containingRect, appRect);
+    }
+
+    private void verifyFrameSizeAndPosition(
+            int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx,
+            Rectangle containingFrame, Rectangle parentFrame) {
+        Assert.assertEquals("Width is incorrect", expectedWidthPx, containingFrame.width);
+        Assert.assertEquals("Height is incorrect", expectedHeightPx, containingFrame.height);
+
+        if (vGravity == GRAVITY_VER_TOP) {
+            Assert.assertEquals("Should be on the top", parentFrame.y, containingFrame.y);
+        } else {
+            Assert.assertEquals("Should be on the bottom",
+                    parentFrame.y + parentFrame.height, containingFrame.y + containingFrame.height);
+        }
+
+        if (hGravity == GRAVITY_HOR_LEFT) {
+            Assert.assertEquals("Should be on the left", parentFrame.x, containingFrame.x);
+        } else {
+            Assert.assertEquals("Should be on the right",
+                    parentFrame.x + parentFrame.width, containingFrame.x + containingFrame.width);
+        }
+    }
+
+    private static int dpToPx(float dp, int densityDpi){
+        return (int) (dp * densityDpi / DISPLAY_DENSITY_DEFAULT + 0.5f);
+    }
+}
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerState.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerState.java
index 1db6829..1a6a3b8 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerState.java
@@ -60,6 +60,10 @@
         boolean retry = false;
         String dump = null;
 
+        CLog.logAndDisplay(INFO, "==============================");
+        CLog.logAndDisplay(INFO, "     ActivityManagerState     ");
+        CLog.logAndDisplay(INFO, "==============================");
+
         do {
             if (retry) {
                 CLog.logAndDisplay(INFO, "***Incomplete AM state. Retrying...");
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
index 049a04a..fcc2bd0 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/ActivityManagerTestBase.java
@@ -122,6 +122,14 @@
         mDevice.executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId);
     }
 
+    protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom)
+            throws Exception {
+        final int taskId = getActivityTaskId(activityName);
+        final String cmd = "am task resize "
+                + taskId + " " + left + " " + top + " " + right + " " + bottom;
+        mDevice.executeShellCommand(cmd);
+    }
+
     // Utility method for debugging, not used directly here, but useful, so kept around.
     protected void printStacksAndTasks() throws DeviceNotAvailableException {
         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
diff --git a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
index a30a2a7..e8b0f59 100644
--- a/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
+++ b/hostsidetests/services/activitymanager/src/android/server/cts/WindowManagerState.java
@@ -37,32 +37,33 @@
     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 =
+    private static final Pattern sWindowPattern =
             Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+)\\}\\:");
-    private final Pattern mStartingWindowPattern =
+    private static final Pattern sStartingWindowPattern =
             Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) Starting (.+)\\}\\:");
-    private final Pattern mExitingWindowPattern =
+    private static final Pattern sExitingWindowPattern =
             Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+) EXITING\\}\\:");
 
-    private final Pattern mFocusedWindowPattern = Pattern.compile(
+    private static final Pattern sFocusedWindowPattern = Pattern.compile(
             "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) (\\S+)\\}");
-    private final Pattern mAppErrorFocusedWindowPattern = Pattern.compile(
+    private static final Pattern sAppErrorFocusedWindowPattern = Pattern.compile(
             "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Application Error\\: (\\S+)\\}");
 
-    private final Pattern mFocusedAppPattern =
+    private static final Pattern sFocusedAppPattern =
             Pattern.compile("mFocusedApp=AppWindowToken\\{(.+) token=Token\\{(.+) "
                     + "ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)");
 
-    private final Pattern mStackIdPattern = Pattern.compile("mStackId=(\\d+)");
+    private static final Pattern sStackIdPattern = Pattern.compile("mStackId=(\\d+)");
 
-    private final Pattern[] mExtractStackExitPatterns = { mStackIdPattern, mWindowPattern,
-            mStartingWindowPattern, mExitingWindowPattern, mFocusedWindowPattern,
-            mAppErrorFocusedWindowPattern, mFocusedAppPattern };
+    private static final Pattern[] sExtractStackExitPatterns = {
+            sStackIdPattern, sWindowPattern, sStartingWindowPattern, sExitingWindowPattern,
+            sFocusedWindowPattern, sAppErrorFocusedWindowPattern, sFocusedAppPattern };
 
     // 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<WindowState> mWindowStates = new ArrayList();
     private List<WindowStack> mStacks = new ArrayList();
+    private List<Display> mDisplays = new ArrayList();
     private String mFocusedWindow = null;
     private String mFocusedApp = null;
     private final LinkedList<String> mSysDump = new LinkedList();
@@ -74,6 +75,9 @@
         boolean retry = false;
         String dump = null;
 
+        CLog.logAndDisplay(INFO, "==============================");
+        CLog.logAndDisplay(INFO, "      WindowManagerState      ");
+        CLog.logAndDisplay(INFO, "==============================");
         do {
             if (retry) {
                 CLog.logAndDisplay(INFO, "***Incomplete WM state. Retrying...");
@@ -117,47 +121,52 @@
         Collections.addAll(mSysDump, sysDump.split("\\n"));
 
         while (!mSysDump.isEmpty()) {
+            final Display display =
+                    Display.create(mSysDump, sExtractStackExitPatterns);
+            if (display != null) {
+                CLog.logAndDisplay(INFO, display.toString());
+                mDisplays.add(display);
+                continue;
+            }
+
             final WindowStack stack =
-                    WindowStack.create(mSysDump, mStackIdPattern, mExtractStackExitPatterns);
+                    WindowStack.create(mSysDump, sStackIdPattern, sExtractStackExitPatterns);
 
             if (stack != null) {
                 mStacks.add(stack);
                 continue;
             }
 
-            final String line = mSysDump.pop().trim();
 
-            Matcher matcher = mWindowPattern.matcher(line);
-            if (matcher.matches()) {
-                CLog.logAndDisplay(INFO, line);
-                final String window = matcher.group(4);
+            final WindowState ws = WindowState.create(mSysDump, sExtractStackExitPatterns);
+            if (ws != null) {
+                CLog.logAndDisplay(INFO, ws.toString());
 
                 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.
-                    matcher = mStartingWindowPattern.matcher(line);
-                    if (matcher.matches()) {
+                    if (ws.isStartingWindow()) {
                         CLog.logAndDisplay(INFO,
                                 "Skipping dump due to starting window transition...");
                         return;
                     }
 
-                    matcher = mExitingWindowPattern.matcher(line);
-                    if (matcher.matches()) {
+                    if (ws.isExitingWindow()) {
                         CLog.logAndDisplay(INFO,
                                 "Skipping dump due to exiting window transition...");
                         return;
                     }
                 }
 
-                CLog.logAndDisplay(INFO, window);
-                mWindows.add(window);
-                mRawWindows.add(line);
+                mWindows.add(ws.getName());
+                mWindowStates.add(ws);
                 continue;
             }
 
-            matcher = mFocusedWindowPattern.matcher(line);
+            final String line = mSysDump.pop().trim();
+
+            Matcher matcher = sFocusedWindowPattern.matcher(line);
             if (matcher.matches()) {
                 CLog.logAndDisplay(INFO, line);
                 final String focusedWindow = matcher.group(3);
@@ -166,7 +175,7 @@
                 continue;
             }
 
-            matcher = mAppErrorFocusedWindowPattern.matcher(line);
+            matcher = sAppErrorFocusedWindowPattern.matcher(line);
             if (matcher.matches()) {
                 CLog.logAndDisplay(INFO, line);
                 final String focusedWindow = matcher.group(3);
@@ -175,7 +184,7 @@
                 continue;
             }
 
-            matcher = mFocusedAppPattern.matcher(line);
+            matcher = sFocusedAppPattern.matcher(line);
             if (matcher.matches()) {
                 CLog.logAndDisplay(INFO, line);
                 final String focusedApp = matcher.group(5);
@@ -189,17 +198,31 @@
     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));
-                }
+        for (WindowState ws : mWindowStates) {
+            if (windowName.equals(ws.getName())) {
+                tokenList.add(ws.getToken());
             }
         }
     }
 
+    void getMatchingWindowState(final String windowName, List<WindowState> windowList) {
+        windowList.clear();
+        for (WindowState ws : mWindowStates) {
+            if (windowName.equals(ws.getName())) {
+                windowList.add(ws);
+            }
+        }
+    }
+
+    Display getDisplay(int displayId) {
+        for (Display display : mDisplays) {
+            if (displayId == display.getDisplayId()) {
+                return display;
+            }
+        }
+        return null;
+    }
+
     String getFrontWindow() {
         return mWindows.get(0);
     }
@@ -246,15 +269,16 @@
     private void reset() {
         mSysDump.clear();
         mStacks.clear();
+        mDisplays.clear();
         mWindows.clear();
-        mRawWindows.clear();
+        mWindowStates.clear();
         mFocusedWindow = null;
         mFocusedApp = null;
     }
 
     static class WindowStack extends WindowContainer {
 
-        private static final Pattern TASK_ID_PATTERN = Pattern.compile("taskId=(\\d+)");
+        private static final Pattern sTaskIdPattern = Pattern.compile("taskId=(\\d+)");
 
         int mStackId;
         ArrayList<WindowTask> mTasks = new ArrayList();
@@ -288,13 +312,13 @@
 
             final List<Pattern> taskExitPatterns = new ArrayList();
             Collections.addAll(taskExitPatterns, exitPatterns);
-            taskExitPatterns.add(TASK_ID_PATTERN);
+            taskExitPatterns.add(sTaskIdPattern);
             final Pattern[] taskExitPatternsArray =
                     taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]);
 
             while (!doneExtracting(dump, exitPatterns)) {
                 final WindowTask task =
-                        WindowTask.create(dump, TASK_ID_PATTERN, taskExitPatternsArray);
+                        WindowTask.create(dump, sTaskIdPattern, taskExitPatternsArray);
 
                 if (task != null) {
                     mTasks.add(task);
@@ -324,10 +348,10 @@
     }
 
     static class WindowTask extends WindowContainer {
-        private static final Pattern TEMP_INSET_BOUNDS_PATTERN =
+        private static final Pattern sTempInsetBoundsPattern =
                 Pattern.compile("mTempInsetBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
 
-        private static final Pattern APP_TOKEN_PATTERN = Pattern.compile(
+        private static final Pattern sAppTokenPattern = Pattern.compile(
                 "Activity #(\\d+) AppWindowToken\\{(\\S+) token=Token\\{(\\S+) "
                 + "ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}\\}\\}");
 
@@ -373,13 +397,13 @@
                     continue;
                 }
 
-                Matcher matcher = TEMP_INSET_BOUNDS_PATTERN.matcher(line);
+                Matcher matcher = sTempInsetBoundsPattern.matcher(line);
                 if (matcher.matches()) {
                     CLog.logAndDisplay(INFO, line);
                     mTempInsetBounds = extractBounds(matcher);
                 }
 
-                matcher = APP_TOKEN_PATTERN.matcher(line);
+                matcher = sAppTokenPattern.matcher(line);
                 if (matcher.matches()) {
                     CLog.logAndDisplay(INFO, line);
                     final String appToken = matcher.group(6);
@@ -392,8 +416,8 @@
     }
 
     static abstract class WindowContainer {
-        protected static final Pattern FULLSCREEN_PATTERN = Pattern.compile("mFullscreen=(\\S+)");
-        protected static final Pattern BOUNDS_PATTERN =
+        protected static final Pattern sFullscreenPattern = Pattern.compile("mFullscreen=(\\S+)");
+        protected static final Pattern sBoundsPattern =
                 Pattern.compile("mBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
 
         protected boolean mFullscreen;
@@ -414,7 +438,7 @@
         }
 
         boolean extractFullscreen(String line) {
-            final Matcher matcher = FULLSCREEN_PATTERN.matcher(line);
+            final Matcher matcher = sFullscreenPattern.matcher(line);
             if (!matcher.matches()) {
                 return false;
             }
@@ -426,7 +450,7 @@
         }
 
         boolean extractBounds(String line) {
-            final Matcher matcher = BOUNDS_PATTERN.matcher(line);
+            final Matcher matcher = sBoundsPattern.matcher(line);
             if (!matcher.matches()) {
                 return false;
             }
@@ -446,6 +470,19 @@
             return rect;
         }
 
+        static void extractMultipleBounds(Matcher matcher, int groupIndex, Rectangle... rectList) {
+            for (Rectangle rect : rectList) {
+                if (rect == null) {
+                    return;
+                }
+                final int left = Integer.valueOf(matcher.group(groupIndex++));
+                final int top = Integer.valueOf(matcher.group(groupIndex++));
+                final int right = Integer.valueOf(matcher.group(groupIndex++));
+                final int bottom = Integer.valueOf(matcher.group(groupIndex++));
+                rect.setBounds(left, top, right - left, bottom - top);
+            }
+        }
+
         Rectangle getBounds() {
             return mBounds;
         }
@@ -454,4 +491,198 @@
             return mFullscreen;
         }
     }
+
+    static class Display extends WindowContainer {
+        private static final String TAG = "[Display] ";
+
+        private static final Pattern sDisplayIdPattern =
+                Pattern.compile("Display: mDisplayId=(\\d+)");
+        private static final Pattern sDisplayInfoPattern =
+                Pattern.compile("(.+) (\\d+)dpi cur=(\\d+)x(\\d+) app=(\\d+)x(\\d+) (.+)");
+
+        private final int mDisplayId;
+        private Rectangle mDisplayRect = new Rectangle();
+        private Rectangle mAppRect = new Rectangle();
+        private int mDpi;
+
+        private Display(int displayId) {
+            mDisplayId = displayId;
+        }
+
+        int getDisplayId() {
+            return mDisplayId;
+        }
+
+        int getDpi() {
+            return mDpi;
+        }
+
+        Rectangle getDisplayRect() {
+            return mDisplayRect;
+        }
+
+        Rectangle getAppRect() {
+            return mAppRect;
+        }
+
+        static Display create(LinkedList<String> dump, Pattern[] exitPatterns) {
+            // TODO: exit pattern for displays?
+            final String line = dump.peek().trim();
+
+            Matcher matcher = sDisplayIdPattern.matcher(line);
+            if (!matcher.matches()) {
+                return null;
+            }
+
+            CLog.logAndDisplay(INFO, TAG + "DISPLAY_ID: " + line);
+            dump.pop();
+
+            final int displayId = Integer.valueOf(matcher.group(1));
+            final Display display = new Display(displayId);
+            display.extract(dump, exitPatterns);
+            return display;
+        }
+
+        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
+            while (!doneExtracting(dump, exitPatterns)) {
+                final String line = dump.pop().trim();
+
+                final Matcher matcher = sDisplayInfoPattern.matcher(line);
+                if (matcher.matches()) {
+                    CLog.logAndDisplay(INFO, TAG + "DISPLAY_INFO: " + line);
+                    mDpi = Integer.valueOf(matcher.group(2));
+
+                    final int displayWidth = Integer.valueOf(matcher.group(3));
+                    final int displayHeight = Integer.valueOf(matcher.group(4));
+                    mDisplayRect.setBounds(0, 0, displayWidth, displayHeight);
+
+                    final int appWidth = Integer.valueOf(matcher.group(5));
+                    final int appHeight = Integer.valueOf(matcher.group(6));
+                    mAppRect.setBounds(0, 0, appWidth, appHeight);
+
+                    // break as we don't need other info for now
+                    break;
+                }
+                // Extract other info here if needed
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Display #" + mDisplayId + ": mDisplayRect=" + mDisplayRect
+                    + " mAppRect=" + mAppRect;
+        }
+    }
+
+    static class WindowState extends WindowContainer {
+        private static final String TAG = "[WindowState] ";
+
+        private static final String RECT_STR = "\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]";
+        private static final Pattern sFramePattern =
+                Pattern.compile("Frames: containing=" + RECT_STR + " parent=" + RECT_STR);
+        private static final Pattern sWindowAssociationPattern =
+                Pattern.compile("mDisplayId=(\\d+) stackId=(\\d+) (.+)");
+
+        private final String mName;
+        private final String mAppToken;
+        private final boolean mStarting;
+        private final boolean mExiting;
+        private int mDisplayId = 0;
+        private Rectangle mContainingFrame = new Rectangle();
+        private Rectangle mParentFrame = new Rectangle();
+
+        private WindowState(Matcher matcher, boolean starting, boolean exiting) {
+            mName = matcher.group(4);
+            mAppToken = matcher.group(2);
+            mStarting = starting;
+            mExiting = exiting;
+        }
+
+        String getName() {
+            return mName;
+        }
+
+        String getToken() {
+            return mAppToken;
+        }
+
+        boolean isStartingWindow() {
+            return mStarting;
+        }
+
+        boolean isExitingWindow() {
+            return mExiting;
+        }
+
+        int getDisplayId() {
+            return mDisplayId;
+        }
+
+        Rectangle getContainingFrame() {
+            return mContainingFrame;
+        }
+
+        Rectangle getParentFrame() {
+            return mParentFrame;
+        }
+
+        static WindowState create(LinkedList<String> dump, Pattern[] exitPatterns) {
+            final String line = dump.peek().trim();
+
+            Matcher matcher = sWindowPattern.matcher(line);
+            if (!matcher.matches()) {
+                return null;
+            }
+
+            CLog.logAndDisplay(INFO, TAG + "WINDOW: " + line);
+            dump.pop();
+
+            final WindowState window;
+            Matcher specialMatcher = sStartingWindowPattern.matcher(line);
+            if (specialMatcher.matches()) {
+                CLog.logAndDisplay(INFO, TAG + "STARTING: " + line);
+                window = new WindowState(specialMatcher, true, false);
+            } else {
+                specialMatcher = sExitingWindowPattern.matcher(line);
+                if (specialMatcher.matches()) {
+                    CLog.logAndDisplay(INFO, TAG + "EXITING: " + line);
+                    window = new WindowState(specialMatcher, false, true);
+                } else {
+                    window = new WindowState(matcher, false, false);
+                }
+            }
+
+            window.extract(dump, exitPatterns);
+            return window;
+        }
+
+        private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
+            while (!doneExtracting(dump, exitPatterns)) {
+                final String line = dump.pop().trim();
+
+                Matcher matcher = sWindowAssociationPattern.matcher(line);
+                if (matcher.matches()) {
+                    CLog.logAndDisplay(INFO, TAG + "WINDOW_ASSOCIATION: " + line);
+                    mDisplayId = Integer.valueOf(matcher.group(1));
+                    continue;
+                }
+
+                matcher = sFramePattern.matcher(line);
+                if (matcher.matches()) {
+                    CLog.logAndDisplay(INFO, TAG + "FRAME: " + line);
+                    extractMultipleBounds(matcher, 1, mContainingFrame, mParentFrame);
+                    continue;
+                }
+
+                // Extract other info here if needed
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "WindowState: {" + mAppToken + " " + mName
+                    + (mStarting ? " STARTING" : "") + (mExiting ? " EXITING" : "") + "}"
+                    + " cf=" + mContainingFrame + " pf=" + mParentFrame;
+        }
+    }
 }