• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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