• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.os.bugreports.tests;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.fail;
22 
23 import android.Manifest;
24 import android.content.Context;
25 import android.os.BugreportManager;
26 import android.os.BugreportManager.BugreportCallback;
27 import android.os.BugreportParams;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.os.ParcelFileDescriptor;
31 import android.util.Log;
32 
33 import androidx.test.InstrumentationRegistry;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Rule;
38 import org.junit.Test;
39 import org.junit.rules.TestName;
40 import org.junit.runner.RunWith;
41 import org.junit.runners.JUnit4;
42 
43 import java.io.File;
44 import java.util.concurrent.Executor;
45 import java.util.concurrent.TimeUnit;
46 
47 
48 /**
49  * Tests for BugreportManager API.
50  */
51 @RunWith(JUnit4.class)
52 public class BugreportManagerTest {
53     @Rule public TestName name = new TestName();
54 
55     private static final String TAG = "BugreportManagerTest";
56     private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
57 
58     private Handler mHandler;
59     private Executor mExecutor;
60     private BugreportManager mBrm;
61     private File mBugreportFile;
62     private File mScreenshotFile;
63     private ParcelFileDescriptor mBugreportFd;
64     private ParcelFileDescriptor mScreenshotFd;
65 
66     @Before
setup()67     public void setup() throws Exception {
68         mHandler = createHandler();
69         mExecutor = (runnable) -> {
70             if (mHandler != null) {
71                 mHandler.post(() -> {
72                     runnable.run();
73                 });
74             }
75         };
76 
77         mBrm = getBugreportManager();
78         mBugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
79         mScreenshotFile = createTempFile("screenshot_" + name.getMethodName(), ".png");
80         mBugreportFd = parcelFd(mBugreportFile);
81         mScreenshotFd = parcelFd(mScreenshotFile);
82 
83         getPermissions();
84     }
85 
86     @After
teardown()87     public void teardown() throws Exception {
88         dropPermissions();
89     }
90 
91 
92     @Test
normalFlow_wifi()93     public void normalFlow_wifi() throws Exception {
94         BugreportCallbackImpl callback = new BugreportCallbackImpl();
95         // wifi bugreport does not take screenshot
96         mBrm.startBugreport(mBugreportFd, null /*screenshotFd = null*/, wifi(),
97                 mExecutor, callback);
98         waitTillDoneOrTimeout(callback);
99 
100         assertThat(callback.isDone()).isTrue();
101         // Wifi bugreports should not receive any progress.
102         assertThat(callback.hasReceivedProgress()).isFalse();
103         // TODO: Because of b/130234145, consent dialog is not shown; so we get a timeout error.
104         // When the bug is fixed, accept consent via UIAutomator and verify contents
105         // of mBugreportFd.
106         assertThat(callback.getErrorCode()).isEqualTo(
107                 BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
108         assertFdsAreClosed(mBugreportFd);
109     }
110 
111     @Test
normalFlow_interactive()112     public void normalFlow_interactive() throws Exception {
113         BugreportCallbackImpl callback = new BugreportCallbackImpl();
114         // interactive bugreport does not take screenshot
115         mBrm.startBugreport(mBugreportFd, null /*screenshotFd = null*/, interactive(),
116                 mExecutor, callback);
117 
118         waitTillDoneOrTimeout(callback);
119         assertThat(callback.isDone()).isTrue();
120         // Interactive bugreports show progress updates.
121         assertThat(callback.hasReceivedProgress()).isTrue();
122         assertThat(callback.getErrorCode()).isEqualTo(
123                 BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
124         assertFdsAreClosed(mBugreportFd);
125     }
126 
127     @Test
normalFlow_full()128     public void normalFlow_full() throws Exception {
129         BugreportCallbackImpl callback = new BugreportCallbackImpl();
130         mBrm.startBugreport(mBugreportFd, mScreenshotFd, full(), mExecutor, callback);
131 
132         waitTillDoneOrTimeout(callback);
133         assertThat(callback.isDone()).isTrue();
134         assertThat(callback.getErrorCode()).isEqualTo(
135                 BugreportCallback.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
136         // bugreport and screenshot files should be empty when user consent timed out.
137         assertThat(mBugreportFile.length()).isEqualTo(0);
138         assertThat(mScreenshotFile.length()).isEqualTo(0);
139         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
140     }
141 
142     @Test
simultaneousBugreportsNotAllowed()143     public void simultaneousBugreportsNotAllowed() throws Exception {
144         // Start bugreport #1
145         BugreportCallbackImpl callback = new BugreportCallbackImpl();
146         mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
147 
148         // Before #1 is done, try to start #2.
149         assertThat(callback.isDone()).isFalse();
150         BugreportCallbackImpl callback2 = new BugreportCallbackImpl();
151         File bugreportFile2 = createTempFile("bugreport_2_" + name.getMethodName(), ".zip");
152         File screenshotFile2 = createTempFile("screenshot_2_" + name.getMethodName(), ".png");
153         ParcelFileDescriptor bugreportFd2 = parcelFd(bugreportFile2);
154         ParcelFileDescriptor screenshotFd2 = parcelFd(screenshotFile2);
155         mBrm.startBugreport(bugreportFd2, screenshotFd2, wifi(), mExecutor, callback2);
156         Thread.sleep(500 /* .5s */);
157 
158         // Verify #2 encounters an error.
159         assertThat(callback2.getErrorCode()).isEqualTo(
160                 BugreportCallback.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
161         assertFdsAreClosed(bugreportFd2, screenshotFd2);
162 
163         // Cancel #1 so we can move on to the next test.
164         mBrm.cancelBugreport();
165         Thread.sleep(500 /* .5s */);
166         assertThat(callback.isDone()).isTrue();
167         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
168     }
169 
170     @Test
cancelBugreport()171     public void cancelBugreport() throws Exception {
172         // Start a bugreport.
173         BugreportCallbackImpl callback = new BugreportCallbackImpl();
174         mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
175 
176         // Verify it's not finished yet.
177         assertThat(callback.isDone()).isFalse();
178 
179         // Try to cancel it, but first without DUMP permission.
180         dropPermissions();
181         try {
182             mBrm.cancelBugreport();
183             fail("Expected cancelBugreport to throw SecurityException without DUMP permission");
184         } catch (SecurityException expected) {
185         }
186         assertThat(callback.isDone()).isFalse();
187 
188         // Try again, with DUMP permission.
189         getPermissions();
190         mBrm.cancelBugreport();
191         Thread.sleep(500 /* .5s */);
192         assertThat(callback.isDone()).isTrue();
193         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
194     }
195 
196     @Test
insufficientPermissions_throwsException()197     public void insufficientPermissions_throwsException() throws Exception {
198         dropPermissions();
199 
200         BugreportCallbackImpl callback = new BugreportCallbackImpl();
201         try {
202             mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
203             fail("Expected startBugreport to throw SecurityException without DUMP permission");
204         } catch (SecurityException expected) {
205         }
206         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
207     }
208 
209     @Test
invalidBugreportMode_throwsException()210     public void invalidBugreportMode_throwsException() throws Exception {
211         BugreportCallbackImpl callback = new BugreportCallbackImpl();
212 
213         try {
214             mBrm.startBugreport(mBugreportFd, mScreenshotFd,
215                     new BugreportParams(25) /* unknown bugreport mode */, mExecutor, callback);
216             fail("Expected to throw IllegalArgumentException with unknown bugreport mode");
217         } catch (IllegalArgumentException expected) {
218         }
219         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
220     }
221 
createHandler()222     private Handler createHandler() {
223         HandlerThread handlerThread = new HandlerThread("BugreportManagerTest");
224         handlerThread.start();
225         return new Handler(handlerThread.getLooper());
226     }
227 
228     /* Implementatiion of {@link BugreportCallback} that offers wrappers around execution result */
229     private static final class BugreportCallbackImpl extends BugreportCallback {
230         private int mErrorCode = -1;
231         private boolean mSuccess = false;
232         private boolean mReceivedProgress = false;
233         private final Object mLock = new Object();
234 
235         @Override
onProgress(float progress)236         public void onProgress(float progress) {
237             synchronized (mLock) {
238                 mReceivedProgress = true;
239             }
240         }
241 
242         @Override
onError(int errorCode)243         public void onError(int errorCode) {
244             synchronized (mLock) {
245                 mErrorCode = errorCode;
246                 Log.d(TAG, "bugreport errored.");
247             }
248         }
249 
250         @Override
onFinished()251         public void onFinished() {
252             synchronized (mLock) {
253                 Log.d(TAG, "bugreport finished.");
254                 mSuccess =  true;
255             }
256         }
257 
258         /* Indicates completion; and ended up with a success or error. */
isDone()259         public boolean isDone() {
260             synchronized (mLock) {
261                 return (mErrorCode != -1) || mSuccess;
262             }
263         }
264 
getErrorCode()265         public int getErrorCode() {
266             synchronized (mLock) {
267                 return mErrorCode;
268             }
269         }
270 
isSuccess()271         public boolean isSuccess() {
272             synchronized (mLock) {
273                 return mSuccess;
274             }
275         }
276 
hasReceivedProgress()277         public boolean hasReceivedProgress() {
278             synchronized (mLock) {
279                 return mReceivedProgress;
280             }
281         }
282     }
283 
getBugreportManager()284     public static BugreportManager getBugreportManager() {
285         Context context = InstrumentationRegistry.getContext();
286         BugreportManager bm =
287                 (BugreportManager) context.getSystemService(Context.BUGREPORT_SERVICE);
288         if (bm == null) {
289             throw new AssertionError("Failed to get BugreportManager");
290         }
291         return bm;
292     }
293 
createTempFile(String prefix, String extension)294     private static File createTempFile(String prefix, String extension) throws Exception {
295         final File f = File.createTempFile(prefix, extension);
296         f.setReadable(true, true);
297         f.setWritable(true, true);
298         f.deleteOnExit();
299         return f;
300     }
301 
parcelFd(File file)302     private static ParcelFileDescriptor parcelFd(File file) throws Exception {
303         return ParcelFileDescriptor.open(file,
304                 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
305     }
306 
dropPermissions()307     private static void dropPermissions() {
308         InstrumentationRegistry.getInstrumentation().getUiAutomation()
309                 .dropShellPermissionIdentity();
310     }
311 
getPermissions()312     private static void getPermissions() {
313         InstrumentationRegistry.getInstrumentation().getUiAutomation()
314                 .adoptShellPermissionIdentity(Manifest.permission.DUMP);
315     }
316 
assertFdIsClosed(ParcelFileDescriptor pfd)317     private static void assertFdIsClosed(ParcelFileDescriptor pfd) {
318         try {
319             int fd = pfd.getFd();
320             fail("Expected ParcelFileDescriptor argument to be closed, but got: " + fd);
321         } catch (IllegalStateException expected) {
322         }
323     }
324 
assertFdsAreClosed(ParcelFileDescriptor... pfds)325     private static void assertFdsAreClosed(ParcelFileDescriptor... pfds) {
326         for (int i = 0; i <  pfds.length; i++) {
327             assertFdIsClosed(pfds[i]);
328         }
329     }
330 
now()331     private static long now() {
332         return System.currentTimeMillis();
333     }
334 
shouldTimeout(long startTimeMs)335     private static boolean shouldTimeout(long startTimeMs) {
336         return now() - startTimeMs >= BUGREPORT_TIMEOUT_MS;
337     }
338 
waitTillDoneOrTimeout(BugreportCallbackImpl callback)339     private static void waitTillDoneOrTimeout(BugreportCallbackImpl callback) throws Exception {
340         long startTimeMs = now();
341         while (!callback.isDone()) {
342             Thread.sleep(1000 /* 1s */);
343             if (shouldTimeout(startTimeMs)) {
344                 break;
345             }
346             Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms");
347         }
348     }
349 
350     /*
351      * Returns a {@link BugreportParams} for wifi only bugreport.
352      *
353      * <p>Wifi bugreports have minimal content and are fast to run. They also suppress progress
354      * updates.
355      */
wifi()356     private static BugreportParams wifi() {
357         return new BugreportParams(BugreportParams.BUGREPORT_MODE_WIFI);
358     }
359 
360     /*
361      * Returns a {@link BugreportParams} for interactive bugreport that offers progress updates.
362      *
363      * <p>This is the typical bugreport taken by users. This can take on the order of minutes to
364      * finish.
365      */
interactive()366     private static BugreportParams interactive() {
367         return new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE);
368     }
369 
370     /*
371      * Returns a {@link BugreportParams} for full bugreport that includes a screenshot.
372      *
373      * <p> This can take on the order of minutes to finish
374      */
full()375     private static BugreportParams full() {
376         return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
377     }
378 }
379