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