• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ActivityManager;
23 import android.app.assist.AssistContent;
24 import android.app.assist.AssistStructure;
25 import android.app.assist.AssistStructure.ViewNode;
26 import android.app.assist.AssistStructure.WindowNode;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.res.Resources;
33 import android.content.res.XmlResourceParser;
34 import android.cts.util.SystemUtil;
35 import android.graphics.Bitmap;
36 import android.graphics.BitmapFactory;
37 import android.graphics.Color;
38 import android.graphics.Point;
39 import android.graphics.Rect;
40 import android.os.Bundle;
41 import android.provider.Settings;
42 import android.test.ActivityInstrumentationTestCase2;
43 import android.util.Log;
44 import android.view.Display;
45 import android.view.LayoutInflater;
46 import android.view.View;
47 import android.view.ViewGroup;
48 import android.view.Window;
49 import android.view.accessibility.AccessibilityNodeInfo;
50 import android.webkit.WebView;
51 import android.widget.EditText;
52 import android.widget.TextView;
53 
54 import java.lang.Math;
55 import java.util.concurrent.CountDownLatch;
56 import java.util.concurrent.TimeUnit;
57 
58 public class AssistTestBase extends ActivityInstrumentationTestCase2<TestStartActivity> {
59     private static final String TAG = "AssistTestBase";
60 
61     protected ActivityManager mActivityManager;
62     protected TestStartActivity mTestActivity;
63     protected AssistContent mAssistContent;
64     protected AssistStructure mAssistStructure;
65     protected boolean mScreenshot;
66     protected Bitmap mAppScreenshot;
67     protected BroadcastReceiver mReceiver;
68     protected Bundle mAssistBundle;
69     protected Context mContext;
70     protected CountDownLatch mLatch, mScreenshotLatch, mHasResumedLatch;
71     protected boolean mScreenshotMatches;
72     private Point mDisplaySize;
73     private String mTestName;
74     private View mView;
75 
AssistTestBase()76     public AssistTestBase() {
77         super(TestStartActivity.class);
78     }
79 
80     @Override
setUp()81     protected void setUp() throws Exception {
82         super.setUp();
83         mContext = getInstrumentation().getTargetContext();
84         SystemUtil.runShellCommand(getInstrumentation(),
85                 "settings put secure assist_structure_enabled 1");
86         SystemUtil.runShellCommand(getInstrumentation(),
87                 "settings put secure assist_screenshot_enabled 1");
88         logContextAndScreenshotSetting();
89 
90         // reset old values
91         mScreenshotMatches = false;
92         mScreenshot = false;
93         mAssistStructure = null;
94         mAssistContent = null;
95         mAssistBundle = null;
96 
97         if (mReceiver != null) {
98             mContext.unregisterReceiver(mReceiver);
99         }
100         mReceiver = new TestResultsReceiver();
101         mContext.registerReceiver(mReceiver,
102             new IntentFilter(Utils.BROADCAST_ASSIST_DATA_INTENT));
103     }
104 
105     @Override
tearDown()106     protected void tearDown() throws Exception {
107         mTestActivity.finish();
108         mContext.sendBroadcast(new Intent(Utils.HIDE_SESSION));
109         if (mReceiver != null) {
110             mContext.unregisterReceiver(mReceiver);
111             mReceiver = null;
112         }
113         super.tearDown();
114     }
115 
116     /**
117      * Starts the shim service activity
118      */
startTestActivity(String testName)119     protected void startTestActivity(String testName) {
120         Intent intent = new Intent();
121         mTestName = testName;
122         intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + testName);
123         intent.setComponent(new ComponentName(getInstrumentation().getContext(),
124                 TestStartActivity.class));
125         intent.putExtra(Utils.TESTCASE_TYPE, testName);
126         setActivityIntent(intent);
127         mTestActivity = getActivity();
128         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
129     }
130 
131     /**
132      * Called when waiting for Assistant's Broadcast Receiver to be setup
133      */
waitForAssistantToBeReady(CountDownLatch latch)134     public void waitForAssistantToBeReady(CountDownLatch latch) throws Exception {
135         Log.i(TAG, "waiting for assistant to be ready before continuing");
136         if (!latch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
137             fail("Assistant was not ready before timeout of: " + Utils.TIMEOUT_MS + "msec");
138         }
139     }
140 
141     /**
142      * Send broadcast to MainInteractionService to start a session
143      */
startSession()144     protected void startSession() {
145         startSession(mTestName, new Bundle());
146     }
147 
startSession(String testName, Bundle extras)148     protected void startSession(String testName, Bundle extras) {
149         Intent intent = new Intent(Utils.BROADCAST_INTENT_START_ASSIST);
150         Log.i(TAG, "passed in class test name is: " + testName);
151         intent.putExtra(Utils.TESTCASE_TYPE, testName);
152         addDimensionsToIntent(intent);
153         intent.putExtras(extras);
154         mContext.sendBroadcast(intent);
155     }
156 
157     /**
158      * Calculate display dimensions (including navbar) to pass along in the given intent.
159      */
addDimensionsToIntent(Intent intent)160     private void addDimensionsToIntent(Intent intent) {
161         if (mDisplaySize == null) {
162             Display display = mTestActivity.getWindowManager().getDefaultDisplay();
163             mDisplaySize = new Point();
164             display.getRealSize(mDisplaySize);
165         }
166         intent.putExtra(Utils.DISPLAY_WIDTH_KEY, mDisplaySize.x);
167         intent.putExtra(Utils.DISPLAY_HEIGHT_KEY, mDisplaySize.y);
168     }
169 
170     /**
171      * Called after startTestActivity. Includes check for receiving context.
172      */
waitForBroadcast()173     protected boolean waitForBroadcast() throws Exception {
174         mTestActivity.start3pApp(mTestName);
175         mTestActivity.startTest(mTestName);
176         return waitForContext();
177     }
178 
waitForContext()179     protected boolean waitForContext() throws Exception {
180         mLatch = new CountDownLatch(1);
181 
182         if (mReceiver != null) {
183             mContext.unregisterReceiver(mReceiver);
184         }
185         mReceiver = new TestResultsReceiver();
186         mContext.registerReceiver(mReceiver,
187                 new IntentFilter(Utils.BROADCAST_ASSIST_DATA_INTENT));
188 
189         if (!mLatch.await(Utils.getAssistDataTimeout(mTestName), TimeUnit.MILLISECONDS)) {
190             fail("Fail to receive broadcast in " + Utils.getAssistDataTimeout(mTestName) + "msec");
191         }
192         Log.i(TAG, "Received broadcast with all information.");
193         return true;
194     }
195 
196     /**
197      * Checks that the nullness of values are what we expect.
198      *
199      * @param isBundleNull True if assistBundle should be null.
200      * @param isStructureNull True if assistStructure should be null.
201      * @param isContentNull True if assistContent should be null.
202      * @param isScreenshotNull True if screenshot should be null.
203      */
verifyAssistDataNullness(boolean isBundleNull, boolean isStructureNull, boolean isContentNull, boolean isScreenshotNull)204     protected void verifyAssistDataNullness(boolean isBundleNull, boolean isStructureNull,
205             boolean isContentNull, boolean isScreenshotNull) {
206 
207         if ((mAssistContent == null) != isContentNull) {
208             fail(String.format("Should %s have been null - AssistContent: %s",
209                     isContentNull ? "" : "not", mAssistContent));
210         }
211 
212         if ((mAssistStructure == null) != isStructureNull) {
213             fail(String.format("Should %s have been null - AssistStructure: %s",
214                     isStructureNull ? "" : "not", mAssistStructure));
215         }
216 
217         if ((mAssistBundle == null) != isBundleNull) {
218             fail(String.format("Should %s have been null - AssistBundle: %s",
219                     isBundleNull ? "" : "not", mAssistBundle));
220         }
221 
222         if (mScreenshot == isScreenshotNull) {
223             fail(String.format("Should %s have been null - Screenshot: %s",
224                     isScreenshotNull ? "":"not", mScreenshot));
225         }
226     }
227 
228     /**
229      * Sends a broadcast with the specified scroll positions to the test app.
230      */
scrollTestApp(int scrollX, int scrollY, boolean scrollTextView, boolean scrollScrollView)231     protected void scrollTestApp(int scrollX, int scrollY, boolean scrollTextView,
232             boolean scrollScrollView) {
233         mTestActivity.scrollText(scrollX, scrollY, scrollTextView, scrollScrollView);
234         Intent intent = null;
235         if (scrollTextView) {
236             intent = new Intent(Utils.SCROLL_TEXTVIEW_ACTION);
237         } else if (scrollScrollView) {
238             intent = new Intent(Utils.SCROLL_SCROLLVIEW_ACTION);
239         }
240         intent.putExtra(Utils.SCROLL_X_POSITION, scrollX);
241         intent.putExtra(Utils.SCROLL_Y_POSITION, scrollY);
242         mContext.sendBroadcast(intent);
243     }
244 
245     /**
246      * Verifies the view hierarchy of the backgroundApp matches the assist structure.
247      *
248      * @param backgroundApp ComponentName of app the assistant is invoked upon
249      * @param isSecureWindow Denotes whether the activity has FLAG_SECURE set
250      */
verifyAssistStructure(ComponentName backgroundApp, boolean isSecureWindow)251     protected void verifyAssistStructure(ComponentName backgroundApp, boolean isSecureWindow) {
252         // Check component name matches
253         assertEquals(backgroundApp.flattenToString(),
254                 mAssistStructure.getActivityComponent().flattenToString());
255 
256         Log.i(TAG, "Traversing down structure for: " + backgroundApp.flattenToString());
257         mView = mTestActivity.findViewById(android.R.id.content).getRootView();
258         verifyHierarchy(mAssistStructure, isSecureWindow);
259     }
260 
logContextAndScreenshotSetting()261     protected void logContextAndScreenshotSetting() {
262         Log.i(TAG, "Context is: " + Settings.Secure.getString(
263                 mContext.getContentResolver(), "assist_structure_enabled"));
264         Log.i(TAG, "Screenshot is: " + Settings.Secure.getString(
265                 mContext.getContentResolver(), "assist_screenshot_enabled"));
266     }
267 
268     /**
269      * Recursively traverse and compare properties in the View hierarchy with the Assist Structure.
270      */
verifyHierarchy(AssistStructure structure, boolean isSecureWindow)271     public void verifyHierarchy(AssistStructure structure, boolean isSecureWindow) {
272         Log.i(TAG, "verifyHierarchy");
273 
274         int numWindows = structure.getWindowNodeCount();
275         // TODO: multiple windows?
276         assertEquals("Number of windows don't match", 1, numWindows);
277         int[] appLocationOnScreen = new int[2];
278         mView.getLocationOnScreen(appLocationOnScreen);
279 
280         for (int i = 0; i < numWindows; i++) {
281             AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
282             Log.i(TAG, "Title: " + windowNode.getTitle());
283             // Verify top level window bounds are as big as the app and pinned to its top-left
284             // corner.
285             assertEquals("Window left position wrong: was " + windowNode.getLeft(),
286                     windowNode.getLeft(), appLocationOnScreen[0]);
287             assertEquals("Window top position wrong: was " + windowNode.getTop(),
288                     windowNode.getTop(), appLocationOnScreen[1]);
289             traverseViewAndStructure(
290                     mView,
291                     windowNode.getRootViewNode(),
292                     isSecureWindow);
293         }
294     }
295 
traverseViewAndStructure(View parentView, ViewNode parentNode, boolean isSecureWindow)296     private void traverseViewAndStructure(View parentView, ViewNode parentNode,
297             boolean isSecureWindow) {
298         ViewGroup parentGroup;
299 
300         if (parentView == null && parentNode == null) {
301             Log.i(TAG, "Views are null, done traversing this branch.");
302             return;
303         } else if (parentNode == null || parentView == null) {
304             fail(String.format("Views don't match. View: %s, Node: %s", parentView, parentNode));
305         }
306 
307         // Debugging
308         Log.i(TAG, "parentView is of type: " + parentView.getClass().getName());
309         if (parentView instanceof ViewGroup) {
310             for (int childInt = 0; childInt < ((ViewGroup) parentView).getChildCount();
311                     childInt++) {
312                 Log.i(TAG,
313                         "viewchild" + childInt + " is of type: "
314                         + ((ViewGroup) parentView).getChildAt(childInt).getClass().getName());
315             }
316         }
317         String parentViewId = null;
318         if (parentView.getId() > 0) {
319             parentViewId = mTestActivity.getResources().getResourceEntryName(parentView.getId());
320             Log.i(TAG, "View ID: " + parentViewId);
321         }
322 
323         Log.i(TAG, "parentNode is of type: " + parentNode.getClassName());
324         for (int nodeInt = 0; nodeInt < parentNode.getChildCount(); nodeInt++) {
325             Log.i(TAG,
326                     "nodechild" + nodeInt + " is of type: "
327                     + parentNode.getChildAt(nodeInt).getClassName());
328         }
329         Log.i(TAG, "Node ID: " + parentNode.getIdEntry());
330 
331         assertEquals("IDs do not match", parentViewId, parentNode.getIdEntry());
332 
333         int numViewChildren = 0;
334         int numNodeChildren = 0;
335         if (parentView instanceof ViewGroup) {
336             numViewChildren = ((ViewGroup) parentView).getChildCount();
337         }
338         numNodeChildren = parentNode.getChildCount();
339 
340         if (isSecureWindow) {
341             assertTrue("ViewNode property isAssistBlocked is false", parentNode.isAssistBlocked());
342             assertEquals("Secure window should only traverse root node.", 0, numNodeChildren);
343             isSecureWindow = false;
344         } else if (parentNode.getClassName().equals("android.webkit.WebView")) {
345             // WebView will also appear to have no children while the node does, traverse node
346             assertTrue("AssistStructure returned a WebView where the view wasn't one",
347                     parentView instanceof WebView);
348 
349             boolean textInWebView = false;
350 
351             for (int i = numNodeChildren - 1; i >= 0; i--) {
352                textInWebView |= traverseWebViewForText(parentNode.getChildAt(i));
353             }
354             assertTrue("Did not find expected strings inside WebView", textInWebView);
355         } else {
356             assertEquals("Number of children did not match.", numViewChildren, numNodeChildren);
357 
358             verifyViewProperties(parentView, parentNode);
359 
360             if (parentView instanceof ViewGroup) {
361                 parentGroup = (ViewGroup) parentView;
362 
363                 // TODO: set a max recursion level
364                 for (int i = numNodeChildren - 1; i >= 0; i--) {
365                     View childView = parentGroup.getChildAt(i);
366                     ViewNode childNode = parentNode.getChildAt(i);
367 
368                     // if isSecureWindow, should not have reached this point.
369                     assertFalse(isSecureWindow);
370                     traverseViewAndStructure(childView, childNode, isSecureWindow);
371                 }
372             }
373         }
374     }
375 
376     /**
377      * Return true if the expected strings are found in the WebView, else fail.
378      */
traverseWebViewForText(ViewNode parentNode)379     private boolean traverseWebViewForText(ViewNode parentNode) {
380         boolean textFound = false;
381         if (parentNode.getText() != null
382                 && parentNode.getText().toString().equals(Utils.WEBVIEW_HTML_GREETING)) {
383             return true;
384         }
385         for (int i = parentNode.getChildCount() - 1; i >= 0; i--) {
386             textFound |= traverseWebViewForText(parentNode.getChildAt(i));
387         }
388         return textFound;
389     }
390 
391     /**
392      * Compare view properties of the view hierarchy with that reported in the assist structure.
393      */
verifyViewProperties(View parentView, ViewNode parentNode)394     private void verifyViewProperties(View parentView, ViewNode parentNode) {
395         assertEquals("Left positions do not match.", parentView.getLeft(), parentNode.getLeft());
396         assertEquals("Top positions do not match.", parentView.getTop(), parentNode.getTop());
397 
398         int viewId = parentView.getId();
399 
400         if (viewId > 0) {
401             if (parentNode.getIdEntry() != null) {
402                 assertEquals("View IDs do not match.",
403                         mTestActivity.getResources().getResourceEntryName(viewId),
404                         parentNode.getIdEntry());
405             }
406         } else {
407             assertNull("View Node should not have an ID.", parentNode.getIdEntry());
408         }
409 
410         Log.i(TAG, "parent text: " + parentNode.getText());
411         if (parentView instanceof TextView) {
412             Log.i(TAG, "view text: " + ((TextView) parentView).getText());
413         }
414 
415 
416         assertEquals("Scroll X does not match.", parentView.getScrollX(), parentNode.getScrollX());
417         assertEquals("Scroll Y does not match.", parentView.getScrollY(), parentNode.getScrollY());
418         assertEquals("Heights do not match.", parentView.getHeight(), parentNode.getHeight());
419         assertEquals("Widths do not match.", parentView.getWidth(), parentNode.getWidth());
420 
421         if (parentView instanceof TextView) {
422             if (parentView instanceof EditText) {
423                 assertEquals("Text selection start does not match",
424                     ((EditText)parentView).getSelectionStart(), parentNode.getTextSelectionStart());
425                 assertEquals("Text selection end does not match",
426                         ((EditText)parentView).getSelectionEnd(), parentNode.getTextSelectionEnd());
427             }
428             TextView textView = (TextView) parentView;
429             assertEquals(textView.getTextSize(), parentNode.getTextSize());
430             String viewString = textView.getText().toString();
431             String nodeString = parentNode.getText().toString();
432 
433             if (parentNode.getScrollX() == 0 && parentNode.getScrollY() == 0) {
434                 Log.i(TAG, "Verifying text within TextView at the beginning");
435                 Log.i(TAG, "view string: " + viewString);
436                 Log.i(TAG, "node string: " + nodeString);
437                 assertTrue("String length is unexpected: original string - " + viewString.length() +
438                                 ", string in AssistData - " + nodeString.length(),
439                         viewString.length() >= nodeString.length());
440                 assertTrue("Expected a longer string to be shown. expected: "
441                                 + Math.min(viewString.length(), 30) + " was: " + nodeString
442                                 .length(),
443                         nodeString.length() >= Math.min(viewString.length(), 30));
444                 for (int x = 0; x < parentNode.getText().length(); x++) {
445                     assertEquals("Char not equal at index: " + x,
446                             ((TextView) parentView).getText().toString().charAt(x),
447                             parentNode.getText().charAt(x));
448                 }
449             } else if (parentNode.getScrollX() == parentView.getWidth()) {
450 
451             }
452         } else {
453             assertNull(parentNode.getText());
454         }
455     }
456 
457     class TestResultsReceiver extends BroadcastReceiver {
458         @Override
onReceive(Context context, Intent intent)459         public void onReceive(Context context, Intent intent) {
460             if (intent.getAction().equalsIgnoreCase(Utils.BROADCAST_ASSIST_DATA_INTENT)) {
461                 Log.i(TAG, "Received broadcast with assist data.");
462                 Bundle assistData = intent.getExtras();
463                 AssistTestBase.this.mAssistBundle = assistData.getBundle(Utils.ASSIST_BUNDLE_KEY);
464                 AssistTestBase.this.mAssistStructure = assistData.getParcelable(
465                         Utils.ASSIST_STRUCTURE_KEY);
466                 AssistTestBase.this.mAssistContent = assistData.getParcelable(
467                         Utils.ASSIST_CONTENT_KEY);
468 
469                 AssistTestBase.this.mScreenshot =
470                         assistData.getBoolean(Utils.ASSIST_SCREENSHOT_KEY, false);
471 
472                 AssistTestBase.this.mScreenshotMatches = assistData.getBoolean(
473                         Utils.COMPARE_SCREENSHOT_KEY, false);
474 
475                 if (mLatch != null) {
476                     Log.i(AssistTestBase.TAG, "counting down latch. received assist data.");
477                     mLatch.countDown();
478                 }
479             } else if (intent.getAction().equals(Utils.APP_3P_HASRESUMED)) {
480                 if (mHasResumedLatch != null) {
481                     mHasResumedLatch.countDown();
482                 }
483             }
484         }
485     }
486 }
487