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