Traverse and compare view hierarchy with assist structure.

Wait until the layout of the third party app has finished being drawn
before starting the VoiceInteractionSession.
View traversal compares positions, scroll positions, widths, heights,
text, IDs, and hierarchy structure between the Assist Structure and
the view rendered to match that or the test third party app.

Bug: 21668302

Change-Id: I0167bdf6afb242ed8a7440ab021e16fc20b608c2
diff --git a/tests/tests/assist/AndroidManifest.xml b/tests/tests/assist/AndroidManifest.xml
index 97bd874..fefdf54 100644
--- a/tests/tests/assist/AndroidManifest.xml
+++ b/tests/tests/assist/AndroidManifest.xml
@@ -23,8 +23,9 @@
 
     <application>
       <uses-library android:name="android.test.runner" />
-      <activity android:name="android.assist.TestStartActivity"
-                android:label="Assist Target">
+      <activity android:name="android.assist.cts.TestStartActivity"
+                android:label="Assist Structure Test App"
+                android:theme="@android:style/Theme.Material.Light">
           <intent-filter>
               <action android:name="android.intent.action.TEST_START_ACTIVITY_ASSIST_STRUCTURE" />
               <action android:name="android.intent.action.TEST_START_ACTIVITY_DISABLE_CONTEXT" />
diff --git a/tests/tests/assist/common/src/android/assist/common/Utils.java b/tests/tests/assist/common/src/android/assist/common/Utils.java
index 8d555da..4bf9412 100644
--- a/tests/tests/assist/common/src/android/assist/common/Utils.java
+++ b/tests/tests/assist/common/src/android/assist/common/Utils.java
@@ -46,9 +46,11 @@
 
     /** Flag Secure Test intent constants */
     public static final String FLAG_SECURE_HASRESUMED = ACTION_PREFIX + "flag_secure_hasResumed";
+    public static final String ASSIST_STRUCTURE_HASRESUMED = ACTION_PREFIX
+            + "assist_structure_hasResumed";
 
     /** Two second timeout for getting back assist context */
-    public static final int TIMEOUT_MS = 2 * 1000; // TODO(awlee): what is the timeout
+    public static final int TIMEOUT_MS = 2 * 1000;
     public static final int ACTIVITY_ONRESUME_TIMEOUT_MS = 4000;
     public static final String EXTRA_REGISTER_RECEIVER = "register_receiver";
 
@@ -66,10 +68,9 @@
      */
     public static final String getTestActivity(String testCaseType) {
         switch (testCaseType) {
-            case ASSIST_STRUCTURE:
-                return "service.AssistStructureActivity";
             case DISABLE_CONTEXT:
                 return "service.DisableContextActivity";
+            case ASSIST_STRUCTURE:
             case FLAG_SECURE:
             case LIFECYCLE:
                 return "service.DelayedAssistantActivity";
@@ -84,9 +85,11 @@
     public static final ComponentName getTestAppComponent(String testCaseType) {
         switch (testCaseType) {
             case ASSIST_STRUCTURE:
-            case DISABLE_CONTEXT:
                 return new ComponentName(
                         "android.assist.testapp", "android.assist.testapp.TestApp");
+            case DISABLE_CONTEXT:
+                return new ComponentName(
+                        "android.assist.testapp", "android.assist.testapp.DisableContextActivity");
             case FLAG_SECURE:
                 return new ComponentName(
                         "android.assist.testapp", "android.assist.testapp.SecureActivity");
diff --git a/tests/tests/assist/res/layout/test_app.xml b/tests/tests/assist/res/layout/test_app.xml
new file mode 100644
index 0000000..3fbfd6d
--- /dev/null
+++ b/tests/tests/assist/res/layout/test_app.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/welcome" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="350dp"
+        android:orientation="vertical"
+        android:layout_gravity="bottom">
+        <FrameLayout
+            android:id="@+id/card1"
+            android:layout_width="match_parent"
+            android:layout_height="150dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginTop="16dp"
+            android:elevation="3dp">
+        </FrameLayout>
+        <View
+            android:id="@+id/card2"
+            android:layout_width="match_parent"
+            android:layout_height="200dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginTop="16dp"
+            android:elevation="3dp"/>
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/assist/res/values/strings.xml b/tests/tests/assist/res/values/strings.xml
new file mode 100644
index 0000000..ae4f16e
--- /dev/null
+++ b/tests/tests/assist/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<resources>
+    <string name="welcome">Hello there!</string>
+</resources>
diff --git a/tests/tests/assist/service/AndroidManifest.xml b/tests/tests/assist/service/AndroidManifest.xml
index cdbeef0..5a22d31 100644
--- a/tests/tests/assist/service/AndroidManifest.xml
+++ b/tests/tests/assist/service/AndroidManifest.xml
@@ -31,14 +31,7 @@
               <action android:name="android.service.voice.VoiceInteractionService" />
           </intent-filter>
       </service>
-      <activity android:name=".AssistStructureActivity" >
-          <intent-filter>
-              <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
-              <category android:name="android.intent.category.DEFAULT" />
-          </intent-filter>
-      </activity>
-      <activity android:name=".DisableContextActivity"
-                android:label="Disabled Context Activity">
+      <activity android:name=".DisableContextActivity" >
           <intent-filter>
               <action android:name="android.intent.action.START_TEST_DISABLE_CONTEXT" />
               <category android:name="android.intent.category.DEFAULT" />
@@ -47,6 +40,7 @@
       <activity android:name=".DelayedAssistantActivity"
                 android:label="Delay Assistant Start Activity">
           <intent-filter>
+              <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
               <action android:name="android.intent.action.START_TEST_LIFECYCLE" />
               <action android:name="android.intent.action.START_TEST_FLAG_SECURE" />
               <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java b/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java
index 31d1694..b7d67f1 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java
@@ -24,7 +24,7 @@
 import android.util.Log;
 
 public class DelayedAssistantActivity extends Activity {
-    static final String TAG = "DelatyedAssistantActivity";
+    static final String TAG = "DelayedAssistantActivity";
 
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/DisableContextActivity.java b/tests/tests/assist/service/src/android/voiceinteraction/service/DisableContextActivity.java
index 52ba7ac..2fd540b 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/DisableContextActivity.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/DisableContextActivity.java
@@ -22,9 +22,6 @@
 import android.os.Bundle;
 import android.util.Log;
 
-/**
- * TODO(awlee): Change context on/off settings and test
- */
 public class DisableContextActivity extends Activity {
     static final String TAG = "DisableContextActivity";
 
@@ -38,8 +35,8 @@
         super.onStart();
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(this, MainInteractionService.class));
+        Log.i(TAG, "Starting service.");
         finish();
-        ComponentName serviceName = startService(intent);
-        Log.i(TAG, "Started service: " + serviceName);
+        startService(intent);
     }
 }
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
index 7530933..fc19e1c 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
@@ -51,7 +51,7 @@
     }
 
     private void maybeStart() {
-       if (mIntent == null || !mReady) {
+        if (mIntent == null || !mReady) {
             Log.wtf(TAG, "Can't start session because either intent is null or onReady() "
                     + "has not been called yet. mIntent = " + mIntent + ", mReady = " + mReady);
         } else {
@@ -60,18 +60,18 @@
                     Log.i(TAG, "Registering receiver to start session later");
                     if (mBroadcastReceiver == null) {
                         mBroadcastReceiver = new MainInteractionServiceBroadcastReceiver();
-                        registerReceiver(mBroadcastReceiver, 
+                        registerReceiver(mBroadcastReceiver,
                                 new IntentFilter(Utils.BROADCAST_INTENT_START_ASSIST));
                     }
                     sendBroadcast(new Intent(Utils.ASSIST_RECEIVER_REGISTERED));
-                } else {
-                    Log.i(TAG, "Yay! about to start session");
-                    showSession(new Bundle(), VoiceInteractionSession.SHOW_WITH_ASSIST |
-                                VoiceInteractionSession.SHOW_WITH_SCREENSHOT);
-                }
+              } else {
+                  Log.i(TAG, "Yay! about to start session");
+                  showSession(new Bundle(), VoiceInteractionSession.SHOW_WITH_ASSIST |
+                          VoiceInteractionSession.SHOW_WITH_SCREENSHOT);
+              }
             } else {
                 Log.wtf(TAG, "**** Not starting MainInteractionService because" +
-                    " it is not set as the current voice interaction service");
+                        " it is not set as the current voice interaction service");
             }
         }
     }
@@ -79,6 +79,7 @@
     private class MainInteractionServiceBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
+            Log.i(MainInteractionService.TAG, "Recieved broadcast to start session now.");
             if (intent.getAction().equals(Utils.BROADCAST_INTENT_START_ASSIST)) {
                 showSession(new Bundle(), SHOW_WITH_ASSIST | SHOW_WITH_SCREENSHOT);
             }
@@ -91,4 +92,4 @@
             unregisterReceiver(mBroadcastReceiver);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
index f297b3e..38d03f8 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -92,7 +92,7 @@
         /*@Nullable*/ AssistContent content) {
         Log.i(TAG, "onHandleAssist");
         Log.i(TAG,
-            String.format("Bundle: %s, Structure: %s, Content: %s", data, structure, content));
+                String.format("Bundle: %s, Structure: %s, Content: %s", data, structure, content));
         super.onHandleAssist(data, structure, content);
 
         // send to test to verify that this is accurate.
diff --git a/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java b/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
index 763ecef..8ff235b 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
@@ -18,7 +18,15 @@
 
 import android.assist.common.Utils;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.provider.Settings;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 
 /**
@@ -26,10 +34,11 @@
  */
 
 public class AssistStructureTest extends AssistTestBase {
-    static final String TAG = "AssistStructureTest";
-
     private static final String TEST_CASE_TYPE = Utils.ASSIST_STRUCTURE;
 
+    private BroadcastReceiver mReceiver;
+    private CountDownLatch mHasResumedLatch = new CountDownLatch(1);
+
     public AssistStructureTest() {
         super();
     }
@@ -37,13 +46,59 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        setUpAndRegisterReceiver();
         startTestActivity(TEST_CASE_TYPE);
-        waitForBroadcast();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+    }
+
+    private void setUpAndRegisterReceiver() {
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+        mReceiver = new AssistStructureTestBroadcastReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Utils.ASSIST_STRUCTURE_HASRESUMED);
+        filter.addAction(Utils.ASSIST_RECEIVER_REGISTERED);
+        mContext.registerReceiver(mReceiver, filter);
+    }
+
+    private void waitForOnResume() throws Exception {
+        Log.i(TAG, "waiting for onResume() before continuing");
+        if (!mHasResumedLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Activity failed to resume in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+        }
     }
 
     public void testAssistStructure() throws Exception {
+        mTestActivity.start3pApp(TEST_CASE_TYPE);
+        mTestActivity.startTest(TEST_CASE_TYPE);
+        waitForAssistantToBeReady();
+        waitForOnResume();
+        startSession();
+        waitForContext();
         verifyAssistDataNullness(false, false, false, false);
+
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE),
-                    false /*FLAG_SECURE set*/);
+                false /*FLAG_SECURE set*/);
+    }
+
+    private class AssistStructureTestBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Utils.ASSIST_STRUCTURE_HASRESUMED)) {
+                mHasResumedLatch.countDown();
+            } else if (action.equals(Utils.ASSIST_RECEIVER_REGISTERED)) {
+                mAssistantReadyLatch.countDown();
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTest.java b/tests/tests/assist/src/android/assist/cts/AssistTest.java
deleted file mode 100644
index 5241c4e..0000000
--- a/tests/tests/assist/src/android/assist/cts/AssistTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 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.assist.cts;
-
-import android.assist.TestStartActivity;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import android.assist.common.Utils;
-
-public class AssistTest extends ActivityInstrumentationTestCase2<TestStartActivity> {
-    static final String TAG = "AssistTest";
-    private static final int TIMEOUT_MS = 2 * 1000;
-
-    public AssistTest() {
-        super(TestStartActivity.class);
-    }
-}
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index a7e7087..25460e5 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -16,16 +16,19 @@
 
 package android.assist.cts;
 
-import android.assist.TestStartActivity;
+import android.assist.cts.TestStartActivity;
 import android.assist.common.Utils;
 
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.cts.util.SystemUtil;
 import android.graphics.Bitmap;
@@ -34,6 +37,11 @@
 import android.provider.Settings;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.TextView;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -49,7 +57,9 @@
     protected Bundle mAssistBundle;
     protected Context mContext;
     protected CountDownLatch mLatch, mAssistantReadyLatch;
+
     private String mTestName;
+    private View mView;
 
     public AssistTestBase() {
         super(TestStartActivity.class);
@@ -61,9 +71,9 @@
         mAssistantReadyLatch = new CountDownLatch(1);
         mContext = getInstrumentation().getTargetContext();
         SystemUtil.runShellCommand(getInstrumentation(),
-            "settings put secure assist_structure_enabled 1");
+                "settings put secure assist_structure_enabled 1");
         SystemUtil.runShellCommand(getInstrumentation(),
-            "settings put secure assist_screenshot_enabled 1");
+                "settings put secure assist_screenshot_enabled 1");
         logContextAndScreenshotSetting();
     }
 
@@ -80,7 +90,7 @@
         mTestName = testName;
         intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + testName);
         intent.setComponent(new ComponentName(getInstrumentation().getContext(),
-            TestStartActivity.class));
+                TestStartActivity.class));
         setActivityIntent(intent);
         mTestActivity = getActivity();
     }
@@ -122,13 +132,14 @@
 
         if (!mLatch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
             fail("Failed to receive broadcast in " + Utils.TIMEOUT_MS + "msec");
-            return false;
         }
+        Log.i(TAG, "Received broadcast with all information.");
         return true;
     }
 
     /**
      * Checks that the nullness of values are what we expect.
+     *
      * @param isBundleNull True if assistBundle should be null.
      * @param isStructureNull True if assistStructure should be null.
      * @param isContentNull True if assistContent should be null.
@@ -159,7 +170,7 @@
     }
 
     /**
-     * Traverses and compares the view heirarchy of the backgroundApp and the view we expect.
+     * Verifies the view hierarchy of the backgroundApp matches the assist structure.
      *
      * @param backgroundApp ComponentName of app the assistant is invoked upon
      * @param isSecureWindow Denotes whether the activity has FLAG_SECURE set
@@ -169,26 +180,154 @@
         assertEquals(backgroundApp.flattenToString(),
             mAssistStructure.getActivityComponent().flattenToString());
 
-        int numWindows = mAssistStructure.getWindowNodeCount();
-        assertEquals(1, numWindows);
-        for (int i = 0; i < numWindows; i++) {
-            AssistStructure.ViewNode node = mAssistStructure.getWindowNodeAt(i).getRootViewNode();
-            // TODO: Actually traverse the view heirarchy and verify it matches what we expect
-            // If isSecureWindow, will not have any children.
-        }
+        Log.i(TAG, "Traversing down structure for: " + backgroundApp.flattenToString());
+        mView = mTestActivity.findViewById(android.R.id.content).getRootView();
+        verifyHierarchy(mAssistStructure, isSecureWindow);
     }
 
     protected void logContextAndScreenshotSetting() {
         Log.i(TAG, "Context is: " + Settings.Secure.getString(
             mContext.getContentResolver(), "assist_structure_enabled"));
         Log.i(TAG, "Screenshot is: " + Settings.Secure.getString(
-            mContext.getContentResolver(), "assist_screenshot_enabled"));
+                mContext.getContentResolver(), "assist_screenshot_enabled"));
+    }
+
+    /**
+     * Recursively traverse and compare properties in the View hierarchy with the Assist Structure.
+     */
+    public void verifyHierarchy(AssistStructure structure, boolean isSecureWindow) {
+        Log.i(TAG, "verifyHierarchy");
+        Window mWindow = mTestActivity.getWindow();
+
+        int numWindows = structure.getWindowNodeCount();
+        // TODO: multiple windows?
+        assertEquals("Number of windows don't match", 1, numWindows);
+
+        for (int i = 0; i < numWindows; i++) {
+            AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
+            Log.i(TAG, "Title: " + windowNode.getTitle());
+            // verify top level window bounds are as big as the screen and pinned to 0,0
+            assertEquals("Window left position wrong: was " + windowNode.getLeft(),
+                    windowNode.getLeft(), 0);
+            assertEquals("Window top position wrong: was " + windowNode.getTop(),
+                    windowNode.getTop(), 0);
+
+            traverseViewAndStructure(
+                    mView,
+                    windowNode.getRootViewNode(),
+                    isSecureWindow);
+        }
+    }
+
+    private void traverseViewAndStructure(View parentView, ViewNode parentNode,
+            boolean isSecureWindow) {
+        ViewGroup parentGroup;
+
+        if (parentView == null && parentNode == null) {
+            Log.i(TAG, "Views are null, done traversing this branch.");
+            return;
+        } else if (parentNode == null || parentView == null) {
+            fail(String.format("Views don't match. View: %s, Node: %s", parentView, parentNode));
+        }
+
+        // Debugging
+        Log.i(TAG, "parentView is of type: " + parentView.getClass().getName());
+        if (parentView instanceof ViewGroup) {
+            for (int childInt = 0; childInt < ((ViewGroup) parentView).getChildCount();
+                    childInt++) {
+                Log.i(TAG,
+                        "viewchild" + childInt + " is of type: "
+                        + ((ViewGroup) parentView).getChildAt(childInt).getClass().getName());
+            }
+        }
+        if (parentView.getId() > 0) {
+            Log.i(TAG, "View ID: "
+                    + mTestActivity.getResources().getResourceEntryName(parentView.getId()));
+        }
+
+        Log.i(TAG, "parentNode is of type: " + parentNode.getClassName());
+        for (int nodeInt = 0; nodeInt < parentNode.getChildCount(); nodeInt++) {
+            Log.i(TAG,
+                    "nodechild" + nodeInt + " is of type: "
+                    + parentNode.getChildAt(nodeInt).getClassName());
+        }
+            Log.i(TAG, "Node ID: " + parentNode.getIdEntry());
+
+        int numViewChildren = 0;
+        int numNodeChildren = 0;
+        if (parentView instanceof ViewGroup) {
+            numViewChildren = ((ViewGroup) parentView).getChildCount();
+        }
+        numNodeChildren = parentNode.getChildCount();
+
+        if  (isSecureWindow) {
+            assertEquals("Secure window should only traverse root node.", 0, numNodeChildren);
+            isSecureWindow = false;
+            return;
+        } else {
+            assertEquals("Number of children did not match.", numViewChildren, numNodeChildren);
+        }
+
+        verifyViewProperties(parentView, parentNode);
+
+        if (parentView instanceof ViewGroup) {
+            parentGroup = (ViewGroup) parentView;
+
+            // TODO: set a max recursion level
+            for (int i = numNodeChildren - 1; i >= 0; i--) {
+                View childView = parentGroup.getChildAt(i);
+                ViewNode childNode = parentNode.getChildAt(i);
+
+                // if isSecureWindow, should not have reached this point.
+                assertFalse(isSecureWindow);
+                traverseViewAndStructure(childView, childNode, isSecureWindow);
+            }
+        }
+    }
+
+    /**
+     * Compare view properties of the view hierarchy with that reported in the assist structure.
+     */
+    private void verifyViewProperties(View parentView, ViewNode parentNode) {
+        assertEquals("Left positions do not match.", parentView.getLeft(), parentNode.getLeft());
+        assertEquals("Top positions do not match.", parentView.getTop(), parentNode.getTop());
+
+        int viewId = parentView.getId();
+
+        if (viewId > 0) {
+            if (parentNode.getIdEntry() != null) {
+                assertEquals("View IDs do not match.",
+                        mTestActivity.getResources().getResourceEntryName(viewId),
+                        parentNode.getIdEntry());
+            }
+        } else {
+            assertNull("View Node should not have an ID.", parentNode.getIdEntry());
+        }
+
+        assertEquals("Scroll X does not match.",
+                parentView.getScrollX(), parentNode.getScrollX());
+
+        assertEquals("Scroll Y does not match.",
+                parentView.getScrollY(), parentNode.getScrollY());
+
+        assertEquals("Heights do not match.", parentView.getHeight(),
+                parentNode.getHeight());
+
+        assertEquals("Widths do not match.", parentView.getWidth(), parentNode.getWidth());
+
+        // TODO: handle unicode/i18n
+        if (parentView instanceof TextView) {
+            assertEquals("Text in TextView does not match.",
+                    ((TextView) parentView).getText().toString(), parentNode.getText());
+        } else {
+            assertNull(parentNode.getText());
+        }
     }
 
     class TestResultsReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equalsIgnoreCase(Utils.BROADCAST_ASSIST_DATA_INTENT)) { // not necessary?
+            if (intent.getAction().equalsIgnoreCase(Utils.BROADCAST_ASSIST_DATA_INTENT)) {
                 Log.i(TAG, "Received broadcast with assist data.");
                 Bundle assistData = intent.getExtras();
                 AssistTestBase.this.mAssistBundle = assistData.getBundle(Utils.ASSIST_BUNDLE_KEY);
diff --git a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
index 9b29407..dc28879 100644
--- a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
+++ b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
@@ -16,7 +16,6 @@
 
 package android.assist.cts;
 
-import android.assist.TestStartActivity;
 import android.assist.common.Utils;
 
 import android.app.Activity;
@@ -31,7 +30,6 @@
 import android.os.Bundle;
 import android.provider.Settings;
 import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
 
 import java.lang.Override;
 import java.util.concurrent.CountDownLatch;
@@ -97,4 +95,4 @@
 
         verifyAssistDataNullness(true, true, true, true);
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
index 40bf7a7..2e9932e 100644
--- a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
@@ -31,7 +31,6 @@
  * invoked on an app with FLAG_SECURE set.
  */
 public class FlagSecureTest extends AssistTestBase {
-
     static final String TAG = "FlagSecureTest";
 
     private static final String TEST_CASE_TYPE = Utils.FLAG_SECURE;
@@ -78,8 +77,13 @@
     }
 
     public void testSecureActivity() throws Exception {
+        mTestActivity.startTest(TEST_CASE_TYPE);
+        waitForAssistantToBeReady();
+        mTestActivity.start3pApp(TEST_CASE_TYPE);
+        waitForOnResume();
+        startSession();
+        waitForContext();
         verifyAssistDataNullness(false, false, false, false);
-
         // verify that we have only the root window and not its children.
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), true);
     }
diff --git a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
index 19a1be5..7451017 100644
--- a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
+++ b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
@@ -16,7 +16,7 @@
 
 package android.assist.cts;
 
-import android.assist.TestStartActivity;
+import android.assist.cts.TestStartActivity;
 import android.assist.common.Utils;
 
 import android.app.Activity;
diff --git a/tests/tests/assist/src/android/assist/TestStartActivity.java b/tests/tests/assist/src/android/assist/cts/TestStartActivity.java
similarity index 89%
rename from tests/tests/assist/src/android/assist/TestStartActivity.java
rename to tests/tests/assist/src/android/assist/cts/TestStartActivity.java
index df9b534..16c924f 100644
--- a/tests/tests/assist/src/android/assist/TestStartActivity.java
+++ b/tests/tests/assist/src/android/assist/cts/TestStartActivity.java
@@ -14,15 +14,21 @@
  * limitations under the License.
  */
 
-package android.assist;
+package android.assist.cts;
 
 import android.assist.common.Utils;
 
 import android.app.Activity;
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
 import android.content.Intent;
 import android.content.ComponentName;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.TextView;
 
 public class TestStartActivity extends Activity {
     static final String TAG = "TestStartActivity";
@@ -31,6 +37,7 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.i(TAG, " in onCreate");
+        setContentView(R.layout.test_app);
     }
 
     @Override
diff --git a/tests/tests/assist/testapp/AndroidManifest.xml b/tests/tests/assist/testapp/AndroidManifest.xml
index 8d6169c..f14cf22 100644
--- a/tests/tests/assist/testapp/AndroidManifest.xml
+++ b/tests/tests/assist/testapp/AndroidManifest.xml
@@ -22,7 +22,7 @@
         <uses-library android:name="android.test.runner" />
 
         <activity android:name="TestApp"
-                android:label="Assist Test App"
+                android:label="Assist Structure Test App"
                 android:theme="@android:style/Theme.Material.Light">
           <intent-filter>
               <action android:name="android.intent.action.TEST_APP_ASSIST_STRUCTURE" />
@@ -30,8 +30,16 @@
               <category android:name="android.intent.category.VOICE" />
           </intent-filter>
         </activity>
+        <activity android:name="DisableContextActivity"
+            android:label="Disable Context Test Activity"
+            android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.TEST_APP_DISABLE_CONTEXT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity android:name="SecureActivity"
-                  android:label="Secure Test App"
+                  android:label="Secure Test Activity"
                   android:theme="@android:style/Theme.Material.Light">
             <intent-filter>
                 <action android:name="android.intent.action.TEST_APP_FLAG_SECURE" />
diff --git a/tests/tests/assist/testapp/res/layout/secure_app.xml b/tests/tests/assist/testapp/res/layout/secure_app.xml
index 9169a37..3b72ad6 100644
--- a/tests/tests/assist/testapp/res/layout/secure_app.xml
+++ b/tests/tests/assist/testapp/res/layout/secure_app.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 <RelativeLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
     <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/welcome" />
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/welcome" />
 </RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/assist/testapp/res/layout/test_app.xml b/tests/tests/assist/testapp/res/layout/test_app.xml
index 9169a37..3fbfd6d 100644
--- a/tests/tests/assist/testapp/res/layout/test_app.xml
+++ b/tests/tests/assist/testapp/res/layout/test_app.xml
@@ -13,13 +13,35 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
     <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/welcome" />
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/welcome" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="350dp"
+        android:orientation="vertical"
+        android:layout_gravity="bottom">
+        <FrameLayout
+            android:id="@+id/card1"
+            android:layout_width="match_parent"
+            android:layout_height="150dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginTop="16dp"
+            android:elevation="3dp">
+        </FrameLayout>
+        <View
+            android:id="@+id/card2"
+            android:layout_width="match_parent"
+            android:layout_height="200dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginTop="16dp"
+            android:elevation="3dp"/>
+    </LinearLayout>
 </RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/assist/testapp/res/values/strings.xml b/tests/tests/assist/testapp/res/values/strings.xml
index a245b36..ae4f16e 100644
--- a/tests/tests/assist/testapp/res/values/strings.xml
+++ b/tests/tests/assist/testapp/res/values/strings.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
 <resources>
     <string name="welcome">Hello there!</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/AssistStructureActivity.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/DisableContextActivity.java
similarity index 65%
rename from tests/tests/assist/service/src/android/voiceinteraction/service/AssistStructureActivity.java
rename to tests/tests/assist/testapp/src/android/voiceinteraction/testapp/DisableContextActivity.java
index 784d63b..ae570d8 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/AssistStructureActivity.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/DisableContextActivity.java
@@ -14,29 +14,25 @@
  * limitations under the License.
  */
 
-package android.assist.service;
+package android.assist.testapp;
 
 import android.app.Activity;
 import android.content.Intent;
-import android.content.ComponentName;
 import android.os.Bundle;
 import android.util.Log;
 
-public class AssistStructureActivity extends Activity {
-    static final String TAG = "VoiceInteractionMain";
+public class DisableContextActivity extends Activity {
+    static final String TAG = "TestApp";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        Log.i(TAG, "TestApp created");
+        setContentView(R.layout.test_app);
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
-        Intent intent = new Intent();
-        intent.setComponent(new ComponentName(this, MainInteractionService.class));
-        Log.i(TAG, "Starting service.");
-        finish();
-        startService(intent);
+    public void onResume() {
+        super.onResume();
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
index d9b2ff2..708061e 100644
--- a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
@@ -16,11 +16,16 @@
 
 package android.assist.testapp;
 
+import android.assist.common.Utils;
+
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
 
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.WindowManager;
 
 public class SecureActivity extends Activity {
@@ -37,7 +42,15 @@
     @Override
     protected void onResume() {
         super.onResume();
-        Log.i(TAG, "Activity has resumed");     
-        sendBroadcast(new Intent("android.intent.action.flag_secure_hasResumed"));
+        Log.i(TAG, "Activity has resumed");
+        final View layout = findViewById(android.R.id.content);
+        ViewTreeObserver vto = layout.getViewTreeObserver();
+        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                sendBroadcast(new Intent(Utils.FLAG_SECURE_HASRESUMED));
+            }
+        });
     }
 }
diff --git a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/TestApp.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/TestApp.java
index 85a9342..ff56ea8 100644
--- a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/TestApp.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/TestApp.java
@@ -16,25 +16,41 @@
 
 package android.assist.testapp;
 
+import android.assist.common.Utils;
+
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import java.lang.Override;
 
 public class TestApp extends Activity {
     static final String TAG = "TestApp";
 
-    Bundle mTestinfo = new Bundle();
-    Bundle mTotalInfo = new Bundle();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.i(TAG, "TestApp created");
-        getLayoutInflater().inflate(R.layout.test_app, null);
+        setContentView(R.layout.test_app);
     }
 
     @Override
     public void onResume() {
         super.onResume();
+        Log.i(TAG, "TestApp has resumed");
+        final View layout = findViewById(android.R.id.content);
+        ViewTreeObserver vto = layout.getViewTreeObserver();
+        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                sendBroadcast(new Intent(Utils.ASSIST_STRUCTURE_HASRESUMED));
+            }
+        });
     }
 }
\ No newline at end of file