1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.assist.cts;
18 
19 import android.assist.cts.TestStartActivity;
20 import android.assist.common.Utils;
21 
22 import android.app.assist.AssistContent;
23 import android.app.assist.AssistStructure;
24 import android.app.assist.AssistStructure.ViewNode;
25 import android.app.assist.AssistStructure.WindowNode;
26 import android.content.BroadcastReceiver;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.res.Resources;
32 import android.content.res.XmlResourceParser;
33 import android.cts.util.SystemUtil;
34 import android.graphics.Bitmap;
35 import android.graphics.BitmapFactory;
36 import android.graphics.Color;
37 import android.graphics.Point;
38 import android.os.Bundle;
39 import android.provider.Settings;
40 import android.test.ActivityInstrumentationTestCase2;
41 import android.util.Log;
42 import android.view.Display;
43 import android.view.LayoutInflater;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.view.Window;
47 import android.widget.TextView;
48 
49 import java.util.concurrent.CountDownLatch;
50 import java.util.concurrent.TimeUnit;
51 
52 public class AssistTestBase extends ActivityInstrumentationTestCase2<TestStartActivity> {
53     private static final String TAG = "AssistTestBase";
54 
55     protected TestStartActivity mTestActivity;
56     protected AssistContent mAssistContent;
57     protected AssistStructure mAssistStructure;
58     protected boolean mScreenshot;
59     protected Bitmap mAppScreenshot;
60     protected BroadcastReceiver mReceiver;
61     protected Bundle mAssistBundle;
62     protected Context mContext;
63     protected CountDownLatch mLatch, mScreenshotLatch, mHasResumedLatch;
64     protected boolean mScreenshotMatches;
65     private Point mDisplaySize;
66     private String mTestName;
67     private View mView;
68 
AssistTestBase()69     public AssistTestBase() {
70         super(TestStartActivity.class);
71     }
72 
73     @Override
setUp()74     protected void setUp() throws Exception {
75         super.setUp();
76         mContext = getInstrumentation().getTargetContext();
77         SystemUtil.runShellCommand(getInstrumentation(),
78                 "settings put secure assist_structure_enabled 1");
79         SystemUtil.runShellCommand(getInstrumentation(),
80                 "settings put secure assist_screenshot_enabled 1");
81         logContextAndScreenshotSetting();
82 
83         // reset old values
84         mScreenshotMatches = false;
85         mScreenshot = false;
86         mAssistStructure = null;
87         mAssistContent = null;
88         mAssistBundle = null;
89 
90         if (mReceiver != null) {
91             mContext.unregisterReceiver(mReceiver);
92         }
93         mReceiver = new TestResultsReceiver();
94         mContext.registerReceiver(mReceiver,
95             new IntentFilter(Utils.BROADCAST_ASSIST_DATA_INTENT));
96     }
97 
98     @Override
tearDown()99     protected void tearDown() throws Exception {
100         mTestActivity.finish();
101         mContext.sendBroadcast(new Intent(Utils.HIDE_SESSION));
102         if (mReceiver != null) {
103             mContext.unregisterReceiver(mReceiver);
104             mReceiver = null;
105         }
106         super.tearDown();
107     }
108 
109     /**
110      * Starts the shim service activity
111      */
startTestActivity(String testName)112     protected void startTestActivity(String testName) {
113         Intent intent = new Intent();
114         mTestName = testName;
115         intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + testName);
116         intent.setComponent(new ComponentName(getInstrumentation().getContext(),
117                 TestStartActivity.class));
118         intent.putExtra(Utils.TESTCASE_TYPE, testName);
119         setActivityIntent(intent);
120         mTestActivity = getActivity();
121     }
122 
123     /**
124      * Called when waiting for Assistant's Broadcast Receiver to be setup
125      */
waitForAssistantToBeReady(CountDownLatch latch)126     public void waitForAssistantToBeReady(CountDownLatch latch) throws Exception {
127         Log.i(TAG, "waiting for assistant to be ready before continuing");
128         if (!latch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
129             fail("Assistant was not ready before timeout of: " + Utils.TIMEOUT_MS + "msec");
130         }
131     }
132 
133     /**
134      * Send broadcast to MainInteractionService to start a session
135      */
startSession()136     protected void startSession() {
137         startSession(mTestName, new Bundle());
138     }
139 
startSession(String testName, Bundle extras)140     protected void startSession(String testName, Bundle extras) {
141         Intent intent = new Intent(Utils.BROADCAST_INTENT_START_ASSIST);
142         Log.i(TAG, "passed in class test name is: " + testName);
143         intent.putExtra(Utils.TESTCASE_TYPE, testName);
144         addDimensionsToIntent(intent);
145         intent.putExtras(extras);
146         mContext.sendBroadcast(intent);
147     }
148 
149     /**
150      * Calculate display dimensions (including navbar) to pass along in the given intent.
151      */
addDimensionsToIntent(Intent intent)152     private void addDimensionsToIntent(Intent intent) {
153         if (mDisplaySize == null) {
154             Display display = mTestActivity.getWindowManager().getDefaultDisplay();
155             mDisplaySize = new Point();
156             display.getRealSize(mDisplaySize);
157         }
158         intent.putExtra(Utils.DISPLAY_WIDTH_KEY, mDisplaySize.x);
159         intent.putExtra(Utils.DISPLAY_HEIGHT_KEY, mDisplaySize.y);
160     }
161 
162     /**
163      * Called after startTestActivity. Includes check for receiving context.
164      */
waitForBroadcast()165     protected boolean waitForBroadcast() throws Exception {
166         mTestActivity.start3pApp(mTestName);
167         mTestActivity.startTest(mTestName);
168         return waitForContext();
169     }
170 
waitForContext()171     protected boolean waitForContext() throws Exception {
172         mLatch = new CountDownLatch(1);
173 
174         if (mReceiver != null) {
175             mContext.unregisterReceiver(mReceiver);
176         }
177         mReceiver = new TestResultsReceiver();
178         IntentFilter filter = new IntentFilter();
179         filter.addAction(Utils.BROADCAST_ASSIST_DATA_INTENT);
180         mContext.registerReceiver(mReceiver, filter);
181         if (!mLatch.await(Utils.getAssistDataTimeout(mTestName), TimeUnit.MILLISECONDS)) {
182             fail("Fail to receive broadcast in " + Utils.getAssistDataTimeout(mTestName) + "msec");
183         }
184         Log.i(TAG, "Received broadcast with all information.");
185         return true;
186     }
187 
188     /**
189      * Checks that the nullness of values are what we expect.
190      *
191      * @param isBundleNull True if assistBundle should be null.
192      * @param isStructureNull True if assistStructure should be null.
193      * @param isContentNull True if assistContent should be null.
194      * @param isScreenshotNull True if screenshot should be null.
195      */
verifyAssistDataNullness(boolean isBundleNull, boolean isStructureNull, boolean isContentNull, boolean isScreenshotNull)196     protected void verifyAssistDataNullness(boolean isBundleNull, boolean isStructureNull,
197             boolean isContentNull, boolean isScreenshotNull) {
198 
199         if ((mAssistContent == null) != isContentNull) {
200             fail(String.format("Should %s have been null - AssistContent: %s",
201                     isContentNull? "":"not", mAssistContent));
202         }
203 
204         if ((mAssistStructure == null) != isStructureNull) {
205             fail(String.format("Should %s have been null - AssistStructure: %s",
206                 isStructureNull ? "" : "not", mAssistStructure));
207         }
208 
209         if ((mAssistBundle == null) != isBundleNull) {
210             fail(String.format("Should %s have been null - AssistBundle: %s",
211                     isBundleNull? "":"not", mAssistBundle));
212         }
213 
214         if (mScreenshot == isScreenshotNull) {
215             fail(String.format("Should %s have been null - Screenshot: %s",
216                     isScreenshotNull? "":"not", mScreenshot));
217         }
218     }
219 
220     /**
221      * Verifies the view hierarchy of the backgroundApp matches the assist structure.
222      *
223      * @param backgroundApp ComponentName of app the assistant is invoked upon
224      * @param isSecureWindow Denotes whether the activity has FLAG_SECURE set
225      */
verifyAssistStructure(ComponentName backgroundApp, boolean isSecureWindow)226     protected void verifyAssistStructure(ComponentName backgroundApp, boolean isSecureWindow) {
227         // Check component name matches
228         assertEquals(backgroundApp.flattenToString(),
229             mAssistStructure.getActivityComponent().flattenToString());
230 
231         Log.i(TAG, "Traversing down structure for: " + backgroundApp.flattenToString());
232         mView = mTestActivity.findViewById(android.R.id.content).getRootView();
233         verifyHierarchy(mAssistStructure, isSecureWindow);
234     }
235 
logContextAndScreenshotSetting()236     protected void logContextAndScreenshotSetting() {
237         Log.i(TAG, "Context is: " + Settings.Secure.getString(
238                 mContext.getContentResolver(), "assist_structure_enabled"));
239         Log.i(TAG, "Screenshot is: " + Settings.Secure.getString(
240                 mContext.getContentResolver(), "assist_screenshot_enabled"));
241     }
242 
243     /**
244      * Recursively traverse and compare properties in the View hierarchy with the Assist Structure.
245      */
verifyHierarchy(AssistStructure structure, boolean isSecureWindow)246     public void verifyHierarchy(AssistStructure structure, boolean isSecureWindow) {
247         Log.i(TAG, "verifyHierarchy");
248         Window mWindow = mTestActivity.getWindow();
249 
250         int numWindows = structure.getWindowNodeCount();
251         // TODO: multiple windows?
252         assertEquals("Number of windows don't match", 1, numWindows);
253 
254         for (int i = 0; i < numWindows; i++) {
255             AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
256             Log.i(TAG, "Title: " + windowNode.getTitle());
257             // verify top level window bounds are as big as the screen and pinned to 0,0
258             assertEquals("Window left position wrong: was " + windowNode.getLeft(),
259                     windowNode.getLeft(), 0);
260             assertEquals("Window top position wrong: was " + windowNode.getTop(),
261                     windowNode.getTop(), 0);
262 
263             traverseViewAndStructure(
264                     mView,
265                     windowNode.getRootViewNode(),
266                     isSecureWindow);
267         }
268     }
269 
traverseViewAndStructure(View parentView, ViewNode parentNode, boolean isSecureWindow)270     private void traverseViewAndStructure(View parentView, ViewNode parentNode,
271             boolean isSecureWindow) {
272         ViewGroup parentGroup;
273 
274         if (parentView == null && parentNode == null) {
275             Log.i(TAG, "Views are null, done traversing this branch.");
276             return;
277         } else if (parentNode == null || parentView == null) {
278             fail(String.format("Views don't match. View: %s, Node: %s", parentView, parentNode));
279         }
280 
281         // Debugging
282         Log.i(TAG, "parentView is of type: " + parentView.getClass().getName());
283         if (parentView instanceof ViewGroup) {
284             for (int childInt = 0; childInt < ((ViewGroup) parentView).getChildCount();
285                     childInt++) {
286                 Log.i(TAG,
287                         "viewchild" + childInt + " is of type: "
288                         + ((ViewGroup) parentView).getChildAt(childInt).getClass().getName());
289             }
290         }
291         if (parentView.getId() > 0) {
292             Log.i(TAG, "View ID: "
293                     + mTestActivity.getResources().getResourceEntryName(parentView.getId()));
294         }
295 
296         Log.i(TAG, "parentNode is of type: " + parentNode.getClassName());
297         for (int nodeInt = 0; nodeInt < parentNode.getChildCount(); nodeInt++) {
298             Log.i(TAG,
299                     "nodechild" + nodeInt + " is of type: "
300                     + parentNode.getChildAt(nodeInt).getClassName());
301         }
302             Log.i(TAG, "Node ID: " + parentNode.getIdEntry());
303 
304         int numViewChildren = 0;
305         int numNodeChildren = 0;
306         if (parentView instanceof ViewGroup) {
307             numViewChildren = ((ViewGroup) parentView).getChildCount();
308         }
309         numNodeChildren = parentNode.getChildCount();
310 
311         if (isSecureWindow) {
312             assertEquals("Secure window should only traverse root node.", 0, numNodeChildren);
313             isSecureWindow = false;
314             return;
315         } else {
316             assertEquals("Number of children did not match.", numViewChildren, numNodeChildren);
317         }
318 
319         verifyViewProperties(parentView, parentNode);
320 
321         if (parentView instanceof ViewGroup) {
322             parentGroup = (ViewGroup) parentView;
323 
324             // TODO: set a max recursion level
325             for (int i = numNodeChildren - 1; i >= 0; i--) {
326                 View childView = parentGroup.getChildAt(i);
327                 ViewNode childNode = parentNode.getChildAt(i);
328 
329                 // if isSecureWindow, should not have reached this point.
330                 assertFalse(isSecureWindow);
331                 traverseViewAndStructure(childView, childNode, isSecureWindow);
332             }
333         }
334     }
335 
336     /**
337      * Compare view properties of the view hierarchy with that reported in the assist structure.
338      */
verifyViewProperties(View parentView, ViewNode parentNode)339     private void verifyViewProperties(View parentView, ViewNode parentNode) {
340         assertEquals("Left positions do not match.", parentView.getLeft(), parentNode.getLeft());
341         assertEquals("Top positions do not match.", parentView.getTop(), parentNode.getTop());
342 
343         int viewId = parentView.getId();
344 
345         if (viewId > 0) {
346             if (parentNode.getIdEntry() != null) {
347                 assertEquals("View IDs do not match.",
348                         mTestActivity.getResources().getResourceEntryName(viewId),
349                         parentNode.getIdEntry());
350             }
351         } else {
352             assertNull("View Node should not have an ID.", parentNode.getIdEntry());
353         }
354 
355         Log.i(TAG, "parent text: " + parentNode.getText());
356         if (parentView instanceof TextView) {
357             Log.i(TAG, "view text: " + ((TextView) parentView).getText());
358         }
359 
360         assertEquals("Scroll X does not match.",
361                 parentView.getScrollX(), parentNode.getScrollX());
362 
363         assertEquals("Scroll Y does not match.",
364                 parentView.getScrollY(), parentNode.getScrollY());
365 
366         assertEquals("Heights do not match.", parentView.getHeight(),
367                 parentNode.getHeight());
368 
369         assertEquals("Widths do not match.", parentView.getWidth(), parentNode.getWidth());
370 
371         // TODO: handle unicode/i18n
372         if (parentView instanceof TextView) {
373             assertEquals("Text in TextView does not match.",
374                     ((TextView) parentView).getText().toString(), parentNode.getText());
375         } else {
376             assertNull(parentNode.getText());
377         }
378     }
379 
380     class TestResultsReceiver extends BroadcastReceiver {
381         @Override
onReceive(Context context, Intent intent)382         public void onReceive(Context context, Intent intent) {
383             if (intent.getAction().equalsIgnoreCase(Utils.BROADCAST_ASSIST_DATA_INTENT)) {
384                 Log.i(TAG, "Received broadcast with assist data.");
385                 Bundle assistData = intent.getExtras();
386                 AssistTestBase.this.mAssistBundle = assistData.getBundle(Utils.ASSIST_BUNDLE_KEY);
387                 AssistTestBase.this.mAssistStructure = assistData.getParcelable(
388                         Utils.ASSIST_STRUCTURE_KEY);
389                 AssistTestBase.this.mAssistContent = assistData.getParcelable(
390                         Utils.ASSIST_CONTENT_KEY);
391 
392                 AssistTestBase.this.mScreenshot =
393                         assistData.getBoolean(Utils.ASSIST_SCREENSHOT_KEY, false);
394 
395                 AssistTestBase.this.mScreenshotMatches = assistData.getBoolean(
396                         Utils.COMPARE_SCREENSHOT_KEY, false);
397 
398                 if (mLatch != null) {
399                     Log.i(AssistTestBase.TAG, "counting down latch. received assist data.");
400                     mLatch.countDown();
401                 }
402             } else if (intent.getAction().equals(Utils.APP_3P_HASRESUMED)) {
403                 if (mHasResumedLatch != null) {
404                     mHasResumedLatch.countDown();
405                 }
406             }
407         }
408     }
409 }
410