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