1 /* 2 * Copyright (C) 2023 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.bugreport.cts_root; 18 19 import static android.app.admin.flags.Flags.FLAG_ONBOARDING_BUGREPORT_STORAGE_BUG_FIX; 20 import static android.app.admin.flags.Flags.FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS; 21 22 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static org.junit.Assert.fail; 27 28 import android.app.AlarmManager; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.os.BugreportManager; 34 import android.os.BugreportManager.BugreportCallback; 35 import android.os.BugreportParams; 36 import android.os.ParcelFileDescriptor; 37 import android.platform.test.annotations.RequiresFlagsEnabled; 38 import android.platform.test.flag.junit.CheckFlagsRule; 39 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 40 41 import androidx.annotation.NonNull; 42 import androidx.test.InstrumentationRegistry; 43 import androidx.test.filters.LargeTest; 44 import androidx.test.runner.AndroidJUnit4; 45 import androidx.test.uiautomator.By; 46 import androidx.test.uiautomator.BySelector; 47 import androidx.test.uiautomator.UiDevice; 48 import androidx.test.uiautomator.UiObject2; 49 import androidx.test.uiautomator.Until; 50 51 import com.android.compatibility.common.util.ShellIdentityUtils; 52 import com.android.compatibility.common.util.SystemUtil; 53 54 import org.junit.AfterClass; 55 import org.junit.Before; 56 import org.junit.BeforeClass; 57 import org.junit.Ignore; 58 import org.junit.Rule; 59 import org.junit.Test; 60 import org.junit.rules.TestName; 61 import org.junit.runner.RunWith; 62 63 import java.io.File; 64 import java.lang.reflect.Method; 65 import java.util.ArrayList; 66 import java.util.List; 67 import java.util.concurrent.CountDownLatch; 68 import java.util.concurrent.TimeUnit; 69 70 /** 71 * Device-side tests for Bugreport Manager API. 72 * 73 * <p>These tests require root to allowlist the test package to use the BugreportManager APIs. 74 */ 75 @RunWith(AndroidJUnit4.class) 76 public class BugreportManagerTest { 77 78 private Context mContext; 79 private BugreportManager mBugreportManager; 80 private Method mGetServiceMethod; 81 82 @Rule 83 public TestName name = new TestName(); 84 85 private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); 86 87 private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(4); 88 private static final int MAX_ALLOWED_BUGREPROTS = 8; 89 private static final String INTENT_BUGREPORT_FINISHED = 90 "com.android.internal.intent.action.BUGREPORT_FINISHED"; 91 92 @Rule 93 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 94 95 96 @Before setup()97 public void setup() throws Exception { 98 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 99 mBugreportManager = mContext.getSystemService(BugreportManager.class); 100 mGetServiceMethod = Class.forName("android.os.ServiceManager").getMethod( 101 "getService", String.class); 102 ensureNoConsentDialogShown(); 103 104 // Unlock before finding/clicking an object. 105 final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 106 device.wakeUp(); 107 device.executeShellCommand("wm dismiss-keyguard"); 108 } 109 110 @BeforeClass classSetup()111 public static void classSetup() { 112 runShellCommand("settings put global auto_time 0"); 113 runShellCommand("svc power stayon true"); 114 // Kill current bugreport, so that it does not interfere with future bugreports. 115 runShellCommand("setprop ctl.stop bugreportd"); 116 } 117 118 @AfterClass classTearDown()119 public static void classTearDown() { 120 // Restore auto time 121 runShellCommand("settings put global auto_time 1"); 122 runShellCommand("svc power stayon false"); 123 // Kill current bugreport, so that it does not interfere with future bugreports. 124 runShellCommand("setprop ctl.stop bugreportd"); 125 } 126 127 @LargeTest 128 @Test testRetrieveBugreportConsentGranted()129 public void testRetrieveBugreportConsentGranted() throws Exception { 130 try { 131 ensureNotConsentlessReport(); 132 File startBugreportFile = createTempFile("startbugreport", ".zip"); 133 CountDownLatch latch = new CountDownLatch(1); 134 BugreportCallbackImpl callback = new BugreportCallbackImpl(latch); 135 mBugreportManager.startBugreport(parcelFd(startBugreportFile), null, 136 new BugreportParams( 137 BugreportParams.BUGREPORT_MODE_ONBOARDING, 138 BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT), 139 mContext.getMainExecutor(), callback); 140 latch.await(4, TimeUnit.MINUTES); 141 assertThat(callback.isSuccess()).isTrue(); 142 // No data should be passed to the FD used to call startBugreport. 143 assertThat(startBugreportFile.length()).isEqualTo(0); 144 String bugreportFileLocation = callback.getBugreportFile(); 145 waitForDumpstateServiceToStop(); 146 147 // Trying to retrieve an unknown bugreport should fail 148 latch = new CountDownLatch(1); 149 callback = new BugreportCallbackImpl(latch); 150 File bugreportFile2 = createTempFile("bugreport2_" + name.getMethodName(), ".zip"); 151 mBugreportManager.retrieveBugreport( 152 "unknown/file.zip", parcelFd(bugreportFile2), 153 mContext.getMainExecutor(), callback); 154 assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue(); 155 assertThat(callback.getErrorCode()).isEqualTo( 156 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE); 157 waitForDumpstateServiceToStop(); 158 159 File bugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip"); 160 // A bugreport was previously generated for this caller. When the consent dialog is invoked 161 // and accepted, the bugreport files should be passed to the calling package. 162 ParcelFileDescriptor bugreportFd = parcelFd(bugreportFile); 163 assertThat(bugreportFd).isNotNull(); 164 latch = new CountDownLatch(1); 165 mBugreportManager.retrieveBugreport(bugreportFileLocation, bugreportFd, 166 mContext.getMainExecutor(), new BugreportCallbackImpl(latch)); 167 shareConsentDialog(ConsentReply.ALLOW); 168 assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue(); 169 assertThat(bugreportFile.length()).isGreaterThan(0); 170 } finally { 171 waitForDumpstateServiceToStop(); 172 // Remove all bugreport files 173 SystemUtil.runShellCommand("rm -f -rR -v /bugreports/"); 174 } 175 } 176 177 178 @LargeTest 179 @Test testRetrieveBugreportConsentDenied()180 public void testRetrieveBugreportConsentDenied() throws Exception { 181 try { 182 // User denies consent, therefore no data should be passed back to the bugreport file. 183 ensureNotConsentlessReport(); 184 CountDownLatch latch = new CountDownLatch(1); 185 BugreportCallbackImpl callback = new BugreportCallbackImpl(latch); 186 mBugreportManager.startBugreport(parcelFd(new File("/dev/null")), 187 null, new BugreportParams(BugreportParams.BUGREPORT_MODE_ONBOARDING, 188 BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT), 189 mContext.getMainExecutor(), callback); 190 latch.await(4, TimeUnit.MINUTES); 191 assertThat(callback.isSuccess()).isTrue(); 192 String bugreportFileLocation = callback.getBugreportFile(); 193 waitForDumpstateServiceToStop(); 194 195 latch = new CountDownLatch(1); 196 callback = new BugreportCallbackImpl(latch); 197 File bugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip"); 198 ParcelFileDescriptor bugreportFd = parcelFd(bugreportFile); 199 assertThat(bugreportFd).isNotNull(); 200 mBugreportManager.retrieveBugreport( 201 bugreportFileLocation, 202 bugreportFd, 203 mContext.getMainExecutor(), 204 callback); 205 shareConsentDialog(ConsentReply.DENY); 206 latch.await(1, TimeUnit.MINUTES); 207 assertThat(callback.getErrorCode()).isEqualTo( 208 BugreportCallback.BUGREPORT_ERROR_USER_DENIED_CONSENT); 209 assertThat(bugreportFile.length()).isEqualTo(0); 210 waitForDumpstateServiceToStop(); 211 212 // Since consent has already been denied, this call should fail because consent cannot 213 // be requested twice for the same bugreport. 214 latch = new CountDownLatch(1); 215 callback = new BugreportCallbackImpl(latch); 216 mBugreportManager.retrieveBugreport(bugreportFileLocation, parcelFd(bugreportFile), 217 mContext.getMainExecutor(), callback); 218 latch.await(1, TimeUnit.MINUTES); 219 assertThat(callback.getErrorCode()).isEqualTo( 220 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE); 221 waitForDumpstateServiceToStop(); 222 } finally { 223 waitForDumpstateServiceToStop(); 224 // Remove all bugreport files 225 SystemUtil.runShellCommand("rm -f -rR -v /bugreports/"); 226 } 227 } 228 229 @LargeTest 230 @Test 231 @RequiresFlagsEnabled(FLAG_ONBOARDING_BUGREPORT_STORAGE_BUG_FIX) testBugreportsLimitReached()232 public void testBugreportsLimitReached() throws Exception { 233 try { 234 List<File> bugreportFiles = new ArrayList<>(); 235 List<String> bugreportFileLocations = new ArrayList<>(); 236 CountDownLatch latch = new CountDownLatch(1); 237 238 for (int i = 0; i < MAX_ALLOWED_BUGREPROTS + 1; i++) { 239 waitForDumpstateServiceToStop(); 240 File bugreportFile = createTempFile( 241 "bugreport_" + name.getMethodName() + "_" + i, ".zip"); 242 bugreportFiles.add(bugreportFile); 243 File startBugreportFile = createTempFile("startbugreport", ".zip"); 244 245 latch = new CountDownLatch(1); 246 BugreportCallbackImpl callback = new BugreportCallbackImpl(latch); 247 248 mBugreportManager.startBugreport(parcelFd(startBugreportFile), null, 249 new BugreportParams( 250 BugreportParams.BUGREPORT_MODE_ONBOARDING, 251 BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT), 252 mContext.getMainExecutor(), callback); 253 254 latch.await(BUGREPORT_TIMEOUT_MS, TimeUnit.MILLISECONDS); 255 assertThat(callback.isSuccess()).isTrue(); 256 bugreportFileLocations.add(callback.getBugreportFile()); 257 waitForDumpstateServiceToStop(); 258 } 259 260 final long newTime = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10); 261 SystemUtil.runWithShellPermissionIdentity(() -> 262 mContext.getSystemService(AlarmManager.class).setTime(newTime)); 263 264 // Trigger a shell bugreport to trigger cleanup logic 265 triggerShellBugreport(BugreportParams.BUGREPORT_MODE_ONBOARDING); 266 267 // The retrieved first bugreport file should be empty. 268 latch = new CountDownLatch(1); 269 BugreportCallbackImpl callback = new BugreportCallbackImpl(latch); 270 mBugreportManager.retrieveBugreport( 271 bugreportFileLocations.getFirst(), parcelFd(bugreportFiles.getFirst()), 272 mContext.getMainExecutor(), callback); 273 ensureNotConsentlessReport(); 274 shareConsentDialog(ConsentReply.ALLOW); 275 assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue(); 276 assertThat(bugreportFiles.getFirst().length()).isEqualTo(0); 277 waitForDumpstateServiceToStop(); 278 279 // The retrieved last bugreport file should not be empty. 280 latch = new CountDownLatch(1); 281 callback = new BugreportCallbackImpl(latch); 282 mBugreportManager.retrieveBugreport( 283 bugreportFileLocations.getLast(), parcelFd(bugreportFiles.getLast()), 284 mContext.getMainExecutor(), callback); 285 ensureNotConsentlessReport(); 286 shareConsentDialog(ConsentReply.ALLOW); 287 assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue(); 288 assertThat(bugreportFiles.getLast().length()).isGreaterThan(0); 289 waitForDumpstateServiceToStop(); 290 } finally { 291 waitForDumpstateServiceToStop(); 292 // Remove all bugreport files 293 SystemUtil.runShellCommand("rm -f -rR -v /bugreports/"); 294 } 295 } 296 297 @LargeTest 298 @Test 299 @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS) testBugreport_skipsConsentForDeferredReportAfterFullReport()300 public void testBugreport_skipsConsentForDeferredReportAfterFullReport() throws Exception { 301 try { 302 ensureNotConsentlessReport(); 303 startFullReport(false); 304 305 startDeferredReport(true); 306 startDeferredReport(true); 307 308 } finally { 309 waitForDumpstateServiceToStop(); 310 // Remove all bugreport files 311 SystemUtil.runShellCommand("rm -f -rR -v /bugreports/"); 312 } 313 } 314 315 @LargeTest 316 @Test 317 @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS) testBugreport_skipConsentForDeferredReportAfterDeferredReport()318 public void testBugreport_skipConsentForDeferredReportAfterDeferredReport() throws Exception { 319 try { 320 ensureNotConsentlessReport(); 321 startDeferredReport(false); 322 323 startDeferredReport(true); 324 325 } finally { 326 waitForDumpstateServiceToStop(); 327 // Remove all bugreport files 328 SystemUtil.runShellCommand("rm -f -rR -v /bugreports/"); 329 } 330 } 331 332 @LargeTest 333 @Test 334 @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS) 335 @Ignore("b/344704922") testBugreport_doesNotSkipConsentForFullReportAfterFullReport()336 public void testBugreport_doesNotSkipConsentForFullReportAfterFullReport() throws Exception { 337 try { 338 ensureNotConsentlessReport(); 339 startFullReport(false); 340 341 startFullReport(false); 342 343 } finally { 344 waitForDumpstateServiceToStop(); 345 // Remove all bugreport files 346 SystemUtil.runShellCommand("rm -f -rR -v /bugreports/"); 347 } 348 } 349 350 @LargeTest 351 @Test 352 @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS) testBugreport_skipConsentForFullReportAfterDeferredReport()353 public void testBugreport_skipConsentForFullReportAfterDeferredReport() throws Exception { 354 try { 355 ensureNotConsentlessReport(); 356 startDeferredReport(false); 357 358 startFullReport(true); 359 360 } finally { 361 waitForDumpstateServiceToStop(); 362 // Remove all bugreport files 363 SystemUtil.runShellCommand("rm -f -rR -v /bugreports/"); 364 } 365 } 366 367 @LargeTest 368 @Test 369 @RequiresFlagsEnabled(FLAG_ONBOARDING_CONSENTLESS_BUGREPORTS) testBugreport_doesNotSkipConsentAfterTimeLimit()370 public void testBugreport_doesNotSkipConsentAfterTimeLimit() throws Exception { 371 try { 372 ensureNotConsentlessReport(); 373 startFullReport(false); 374 final long newTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(3); 375 SystemUtil.runWithShellPermissionIdentity(() -> 376 mContext.getSystemService(AlarmManager.class).setTime(newTime)); 377 378 startDeferredReport(false); 379 380 } finally { 381 waitForDumpstateServiceToStop(); 382 // Remove all bugreport files 383 SystemUtil.runShellCommand("rm -f -rR -v /bugreports/"); 384 } 385 } 386 ensureNotConsentlessReport()387 private void ensureNotConsentlessReport() { 388 final long time = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(60); 389 SystemUtil.runWithShellPermissionIdentity(() -> 390 mContext.getSystemService(AlarmManager.class).setTime(time)); 391 assertThat(System.currentTimeMillis()).isGreaterThan(time); 392 } 393 startFullReport(boolean skipConsent)394 private void startFullReport(boolean skipConsent) throws Exception { 395 waitForDumpstateServiceToStop(); 396 File bugreportFile = createTempFile("startbugreport", ".zip"); 397 CountDownLatch latch = new CountDownLatch(1); 398 BugreportCallbackImpl callback = new BugreportCallbackImpl(latch); 399 mBugreportManager.startBugreport(parcelFd(bugreportFile), null, 400 new BugreportParams(BugreportParams.BUGREPORT_MODE_ONBOARDING, 0), 401 mContext.getMainExecutor(), callback); 402 callback.waitForUiReady(); 403 if (!skipConsent) { 404 shareConsentDialog(ConsentReply.ALLOW); 405 } 406 407 latch.await(2, TimeUnit.MINUTES); 408 assertThat(callback.isSuccess()).isTrue(); 409 // No data should be passed to the FD used to call startBugreport. 410 assertThat(bugreportFile.length()).isGreaterThan(0); 411 waitForDumpstateServiceToStop(); 412 } 413 startDeferredReport(boolean skipConsent)414 private void startDeferredReport(boolean skipConsent) throws Exception { 415 waitForDumpstateServiceToStop(); 416 File bugreportFile = createTempFile("startbugreport", ".zip"); 417 CountDownLatch latch = new CountDownLatch(1); 418 BugreportCallbackImpl callback = new BugreportCallbackImpl(latch); 419 mBugreportManager.startBugreport(parcelFd(bugreportFile), null, 420 new BugreportParams( 421 BugreportParams.BUGREPORT_MODE_ONBOARDING, 422 BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT), 423 mContext.getMainExecutor(), callback); 424 425 latch.await(1, TimeUnit.MINUTES); 426 assertThat(callback.isSuccess()).isTrue(); 427 String location = callback.getBugreportFile(); 428 waitForDumpstateServiceToStop(); 429 430 431 // The retrieved bugreport file should not be empty. 432 latch = new CountDownLatch(1); 433 callback = new BugreportCallbackImpl(latch); 434 mBugreportManager.retrieveBugreport( 435 location, parcelFd(bugreportFile), 436 mContext.getMainExecutor(), callback); 437 if (!skipConsent) { 438 shareConsentDialog(ConsentReply.ALLOW); 439 } 440 assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue(); 441 assertThat(bugreportFile.length()).isGreaterThan(0); 442 waitForDumpstateServiceToStop(); 443 } 444 triggerShellBugreport(int type)445 private void triggerShellBugreport(int type) throws Exception { 446 BugreportBroadcastReceiver br = new BugreportBroadcastReceiver(); 447 final IntentFilter intentFilter = new IntentFilter(INTENT_BUGREPORT_FINISHED); 448 mContext.registerReceiver(br, intentFilter, Context.RECEIVER_EXPORTED); 449 final BugreportParams params = new BugreportParams(type); 450 mBugreportManager.requestBugreport(params, "" /* shareTitle */, "" /* shareDescription */); 451 452 try { 453 br.waitForBugreportFinished(); 454 } finally { 455 // The latch may fail for a number of reasons but we still need to unregister the 456 // BroadcastReceiver. 457 mContext.unregisterReceiver(br); 458 } 459 460 Intent response = br.getBugreportFinishedIntent(); 461 assertThat(response.getAction()).isEqualTo(intentFilter.getAction(0)); 462 waitForDumpstateServiceToStop(); 463 } 464 465 private class BugreportBroadcastReceiver extends BroadcastReceiver { 466 Intent bugreportFinishedIntent = null; 467 final CountDownLatch latch; 468 BugreportBroadcastReceiver()469 BugreportBroadcastReceiver() { 470 latch = new CountDownLatch(1); 471 } 472 473 @Override onReceive(Context context, Intent intent)474 public void onReceive(Context context, Intent intent) { 475 setBugreportFinishedIntent(intent); 476 latch.countDown(); 477 } 478 setBugreportFinishedIntent(Intent intent)479 private void setBugreportFinishedIntent(Intent intent) { 480 bugreportFinishedIntent = intent; 481 } 482 getBugreportFinishedIntent()483 public Intent getBugreportFinishedIntent() { 484 return bugreportFinishedIntent; 485 } 486 waitForBugreportFinished()487 public void waitForBugreportFinished() throws Exception { 488 if (!latch.await(BUGREPORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 489 throw new Exception("Failed to receive BUGREPORT_FINISHED in " 490 + BUGREPORT_TIMEOUT_MS + " ms."); 491 } 492 } 493 } 494 parcelFd(File file)495 private ParcelFileDescriptor parcelFd(File file) throws Exception { 496 return ParcelFileDescriptor.open(file, 497 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND); 498 } 499 createTempFile(String prefix, String extension)500 private static File createTempFile(String prefix, String extension) throws Exception { 501 final File f = File.createTempFile(prefix, extension); 502 f.setReadable(true, true); 503 f.setWritable(true, true); 504 505 f.deleteOnExit(); 506 return f; 507 } 508 509 private static final class BugreportCallbackImpl extends BugreportCallback { 510 private int mErrorCode = -1; 511 private boolean mSuccess = false; 512 private String mBugreportFile; 513 private final Object mLock = new Object(); 514 private final CountDownLatch mUiReadyLatch = new CountDownLatch(1); 515 516 private final CountDownLatch mLatch; 517 BugreportCallbackImpl(CountDownLatch latch)518 BugreportCallbackImpl(CountDownLatch latch) { 519 mLatch = latch; 520 } 521 522 @Override onError(int errorCode)523 public void onError(int errorCode) { 524 synchronized (mLock) { 525 mErrorCode = errorCode; 526 mLatch.countDown(); 527 } 528 } 529 530 @Override onEarlyReportFinished()531 public void onEarlyReportFinished() { 532 mUiReadyLatch.countDown(); 533 } 534 535 /** 536 * Wait for onEarlyReportFinished to be called. If this invocation of 537 * startBugreport requires user consent, the dialog will show then; 538 * otherwise, it won't. 539 */ waitForUiReady()540 public void waitForUiReady() throws Exception { 541 mUiReadyLatch.await(30, TimeUnit.SECONDS); 542 } 543 544 @Override onFinished(String bugreportFile)545 public void onFinished(String bugreportFile) { 546 synchronized (mLock) { 547 mBugreportFile = bugreportFile; 548 mLatch.countDown(); 549 mSuccess = true; 550 } 551 } 552 553 @Override onFinished()554 public void onFinished() { 555 synchronized (mLock) { 556 mLatch.countDown(); 557 mSuccess = true; 558 } 559 } 560 getErrorCode()561 public int getErrorCode() { 562 synchronized (mLock) { 563 return mErrorCode; 564 } 565 } 566 isSuccess()567 public boolean isSuccess() { 568 synchronized (mLock) { 569 return mSuccess; 570 } 571 } 572 getBugreportFile()573 public String getBugreportFile() { 574 synchronized (mLock) { 575 return mBugreportFile; 576 } 577 } 578 } 579 580 private enum ConsentReply { 581 ALLOW, 582 DENY, 583 TIMEOUT 584 } 585 586 /* 587 * Ensure the consent dialog is shown and take action according to <code>consentReply<code/>. 588 * It will fail if the dialog is not shown when <code>ignoreNotFound<code/> is false. 589 */ shareConsentDialog(@onNull ConsentReply consentReply)590 private void shareConsentDialog(@NonNull ConsentReply consentReply) throws Exception { 591 final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 592 593 final BySelector consentTitleObj = By.res("android", "alertTitle"); 594 if (!device.wait(Until.hasObject(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS)) { 595 fail("The consent dialog is not found"); 596 } 597 if (consentReply.equals(ConsentReply.TIMEOUT)) { 598 return; 599 } 600 final BySelector selector; 601 if (consentReply.equals(ConsentReply.ALLOW)) { 602 selector = By.res("android", "button1"); 603 } else { // ConsentReply.DENY 604 selector = By.res("android", "button2"); 605 } 606 final UiObject2 btnObj = device.findObject(selector); 607 assertThat(btnObj).isNotNull(); 608 btnObj.click(); 609 610 assertThat(device.wait(Until.gone(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS)).isTrue(); 611 } 612 613 /* 614 * Ensure the consent dialog is shown and take action according to <code>consentReply<code/>. 615 * It will fail if the dialog is not shown when <code>ignoreNotFound<code/> is false. 616 */ ensureNoConsentDialogShown()617 private void ensureNoConsentDialogShown() throws Exception { 618 final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 619 620 final BySelector consentTitleObj = By.res("android", "alertTitle"); 621 if (!device.wait(Until.hasObject(consentTitleObj), TimeUnit.SECONDS.toMillis(2))) { 622 return; 623 } 624 final BySelector selector = By.res("android", "button2"); 625 final UiObject2 btnObj = device.findObject(selector); 626 if (btnObj == null) { 627 return; 628 } 629 btnObj.click(); 630 631 device.wait(Until.gone(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS); 632 } 633 634 635 /** Waits for the dumpstate service to stop, for up to 5 seconds. */ isDumpstateServiceStopped()636 private boolean isDumpstateServiceStopped() throws Exception { 637 // If getService() returns null, the service has stopped. 638 return ShellIdentityUtils.invokeMethodWithShellPermissions(mGetServiceMethod, (m) -> { 639 try { 640 return m.invoke(null, "dumpstate"); 641 } catch (Exception e) { 642 return null; 643 } 644 }) == null; 645 } 646 647 private void waitForDumpstateServiceToStop() throws Exception { 648 int pollingIntervalMillis = 100; 649 650 for (int i = 0; i < 10; i++) { 651 int numPolls = 50; 652 while (numPolls-- > 0) { 653 if (isDumpstateServiceStopped()) { 654 break; 655 } 656 Thread.sleep(pollingIntervalMillis); 657 } 658 } 659 if (isDumpstateServiceStopped()) { 660 return; 661 } 662 fail("Dumpstate did not stop within 25 seconds"); 663 } 664 } 665