• 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 com.android.compatibility.common.util.SystemUtil.runShellCommand;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.fail;
24 
25 import android.content.Context;
26 import android.os.BugreportManager;
27 import android.os.BugreportManager.BugreportCallback;
28 import android.os.BugreportParams;
29 import android.os.ParcelFileDescriptor;
30 
31 import androidx.annotation.NonNull;
32 import androidx.test.InstrumentationRegistry;
33 import androidx.test.filters.LargeTest;
34 import androidx.test.runner.AndroidJUnit4;
35 import androidx.test.uiautomator.By;
36 import androidx.test.uiautomator.BySelector;
37 import androidx.test.uiautomator.UiDevice;
38 import androidx.test.uiautomator.UiObject2;
39 import androidx.test.uiautomator.Until;
40 
41 import org.junit.After;
42 import org.junit.Before;
43 import org.junit.Rule;
44 import org.junit.Test;
45 import org.junit.rules.TestName;
46 import org.junit.runner.RunWith;
47 
48 import java.io.File;
49 import java.lang.reflect.Method;
50 import java.util.concurrent.CountDownLatch;
51 import java.util.concurrent.TimeUnit;
52 
53 /**
54  * Device-side tests for Bugreport Manager API.
55  *
56  * <p>These tests require root to allowlist the test package to use the BugreportManager APIs.
57  */
58 @RunWith(AndroidJUnit4.class)
59 public class BugreportManagerTest {
60 
61     private Context mContext;
62     private BugreportManager mBugreportManager;
63 
64     @Rule
65     public TestName name = new TestName();
66 
67     private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
68 
69     @Before
setup()70     public void setup() {
71         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
72         mBugreportManager = mContext.getSystemService(BugreportManager.class);
73         // Kill current bugreport, so that it does not interfere with future bugreports.
74         runShellCommand("setprop ctl.stop bugreportd");
75     }
76 
77     @After
tearDown()78     public void tearDown() {
79         // Kill current bugreport, so that it does not interfere with future bugreports.
80         runShellCommand("setprop ctl.stop bugreportd");
81     }
82 
83     @LargeTest
84     @Test
testRetrieveBugreportConsentGranted()85     public void testRetrieveBugreportConsentGranted() throws Exception {
86         File bugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
87         File startBugreportFile = createTempFile("startbugreport", ".zip");
88         CountDownLatch latch = new CountDownLatch(1);
89         BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
90         mBugreportManager.startBugreport(parcelFd(startBugreportFile), null,
91                 new BugreportParams(
92                         BugreportParams.BUGREPORT_MODE_INTERACTIVE,
93                         BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT),
94                 mContext.getMainExecutor(), callback);
95         latch.await(4, TimeUnit.MINUTES);
96         assertThat(callback.isSuccess()).isTrue();
97         // No data should be passed to the FD used to call startBugreport.
98         assertThat(startBugreportFile.length()).isEqualTo(0);
99         String bugreportFileLocation = callback.getBugreportFile();
100         waitForDumpstateServiceToStop();
101 
102 
103 
104         // Trying to retrieve an unknown bugreport should fail
105         latch = new CountDownLatch(1);
106         callback = new BugreportCallbackImpl(latch);
107         File bugreportFile2 = createTempFile("bugreport2_" + name.getMethodName(), ".zip");
108         mBugreportManager.retrieveBugreport(
109                 "unknown/file.zip", parcelFd(bugreportFile2),
110                 mContext.getMainExecutor(), callback);
111         assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
112         assertThat(callback.getErrorCode()).isEqualTo(
113                 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
114 
115         // A bugreport was previously generated for this caller. When the consent dialog is invoked
116         // and accepted, the bugreport files should be passed to the calling package.
117         ParcelFileDescriptor bugreportFd = parcelFd(bugreportFile);
118         assertThat(bugreportFd).isNotNull();
119         latch = new CountDownLatch(1);
120         mBugreportManager.retrieveBugreport(bugreportFileLocation, bugreportFd,
121                 mContext.getMainExecutor(), new BugreportCallbackImpl(latch));
122         shareConsentDialog(ConsentReply.ALLOW);
123         assertThat(latch.await(1, TimeUnit.MINUTES)).isTrue();
124         assertThat(bugreportFile.length()).isGreaterThan(0);
125     }
126 
127 
128     @LargeTest
129     @Test
testRetrieveBugreportConsentDenied()130     public void testRetrieveBugreportConsentDenied() throws Exception {
131         File bugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
132 
133         // User denies consent, therefore no data should be passed back to the bugreport file.
134         CountDownLatch latch = new CountDownLatch(1);
135         BugreportCallbackImpl callback = new BugreportCallbackImpl(latch);
136         mBugreportManager.startBugreport(parcelFd(new File("/dev/null")),
137                 null, new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE,
138                 BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT),
139                 mContext.getMainExecutor(), callback);
140         latch.await(4, TimeUnit.MINUTES);
141         assertThat(callback.isSuccess()).isTrue();
142         String bugreportFileLocation = callback.getBugreportFile();
143         waitForDumpstateServiceToStop();
144 
145         latch = new CountDownLatch(1);
146         ParcelFileDescriptor bugreportFd = parcelFd(bugreportFile);
147         assertThat(bugreportFd).isNotNull();
148         mBugreportManager.retrieveBugreport(
149                 bugreportFileLocation,
150                 bugreportFd,
151                 mContext.getMainExecutor(),
152                 callback);
153         shareConsentDialog(ConsentReply.DENY);
154         latch.await(1, TimeUnit.MINUTES);
155         assertThat(callback.getErrorCode()).isEqualTo(
156                 BugreportCallback.BUGREPORT_ERROR_USER_DENIED_CONSENT);
157         assertThat(bugreportFile.length()).isEqualTo(0);
158 
159         // Since consent has already been denied, this call should fail because consent cannot
160         // be requested twice for the same bugreport.
161         latch = new CountDownLatch(1);
162         callback = new BugreportCallbackImpl(latch);
163         mBugreportManager.retrieveBugreport(bugreportFileLocation, parcelFd(bugreportFile),
164                 mContext.getMainExecutor(), callback);
165         latch.await(1, TimeUnit.MINUTES);
166         assertThat(callback.getErrorCode()).isEqualTo(
167                 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
168     }
169 
parcelFd(File file)170     private ParcelFileDescriptor parcelFd(File file) throws Exception {
171         return ParcelFileDescriptor.open(file,
172             ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
173     }
174 
createTempFile(String prefix, String extension)175     private static File createTempFile(String prefix, String extension) throws Exception {
176         final File f = File.createTempFile(prefix, extension);
177         f.setReadable(true, true);
178         f.setWritable(true, true);
179 
180         f.deleteOnExit();
181         return f;
182     }
183 
184     private static final class BugreportCallbackImpl extends BugreportCallback {
185         private int mErrorCode = -1;
186         private boolean mSuccess = false;
187         private String mBugreportFile;
188         private final Object mLock = new Object();
189 
190         private final CountDownLatch mLatch;
191 
BugreportCallbackImpl(CountDownLatch latch)192         BugreportCallbackImpl(CountDownLatch latch) {
193             mLatch = latch;
194         }
195 
196         @Override
onError(int errorCode)197         public void onError(int errorCode) {
198             synchronized (mLock) {
199                 mErrorCode = errorCode;
200                 mLatch.countDown();
201             }
202         }
203 
204         @Override
onFinished(String bugreportFile)205         public void onFinished(String bugreportFile) {
206             synchronized (mLock) {
207                 mBugreportFile = bugreportFile;
208                 mLatch.countDown();
209                 mSuccess =  true;
210             }
211         }
212 
213         @Override
onFinished()214         public void onFinished() {
215             synchronized (mLock) {
216                 mLatch.countDown();
217                 mSuccess = true;
218             }
219         }
220 
getErrorCode()221         public int getErrorCode() {
222             synchronized (mLock) {
223                 return mErrorCode;
224             }
225         }
226 
isSuccess()227         public boolean isSuccess() {
228             synchronized (mLock) {
229                 return mSuccess;
230             }
231         }
232 
getBugreportFile()233         public String getBugreportFile() {
234             synchronized (mLock) {
235                 return mBugreportFile;
236             }
237         }
238     }
239 
240     private enum ConsentReply {
241         ALLOW,
242         DENY,
243         TIMEOUT
244     }
245 
246     /*
247      * Ensure the consent dialog is shown and take action according to <code>consentReply<code/>.
248      * It will fail if the dialog is not shown when <code>ignoreNotFound<code/> is false.
249      */
shareConsentDialog(@onNull ConsentReply consentReply)250     private void shareConsentDialog(@NonNull ConsentReply consentReply) throws Exception {
251         final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
252 
253         // Unlock before finding/clicking an object.
254         device.wakeUp();
255         device.executeShellCommand("wm dismiss-keyguard");
256 
257         final BySelector consentTitleObj = By.res("android", "alertTitle");
258         if (!device.wait(Until.hasObject(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS)) {
259             fail("The consent dialog is not found");
260         }
261         if (consentReply.equals(ConsentReply.TIMEOUT)) {
262             return;
263         }
264         final BySelector selector;
265         if (consentReply.equals(ConsentReply.ALLOW)) {
266             selector = By.res("android", "button1");
267         } else { // ConsentReply.DENY
268             selector = By.res("android", "button2");
269         }
270         final UiObject2 btnObj = device.findObject(selector);
271         assertThat(btnObj).isNotNull();
272         btnObj.click();
273 
274         assertThat(device.wait(Until.gone(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS)).isTrue();
275     }
276 
277 
278     /** Waits for the dumpstate service to stop, for up to 5 seconds. */
waitForDumpstateServiceToStop()279     private void waitForDumpstateServiceToStop() throws Exception {
280         int pollingIntervalMillis = 100;
281         int numPolls = 50;
282         Method method = Class.forName("android.os.ServiceManager").getMethod(
283                 "getService", String.class);
284         while (numPolls-- > 0) {
285             // If getService() returns null, the service has stopped.
286             if (method.invoke(null, "dumpstate") == null) {
287                 return;
288             }
289             Thread.sleep(pollingIntervalMillis);
290         }
291         fail("Dumpstate did not stop within 5 seconds");
292     }
293 }
294