• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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;
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.Assume.assumeFalse;
24 
25 import android.Manifest;
26 import android.app.ActivityManager;
27 import android.app.UiAutomation;
28 import android.content.BroadcastReceiver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.PackageManager;
33 import android.os.BugreportManager;
34 import android.os.BugreportParams;
35 import android.os.Build;
36 import android.os.ParcelFileDescriptor;
37 import android.os.UserHandle;
38 import android.platform.test.annotations.PlatinumTest;
39 import android.text.TextUtils;
40 import android.util.Pair;
41 
42 import androidx.test.InstrumentationRegistry;
43 import androidx.test.filters.LargeTest;
44 import androidx.test.runner.AndroidJUnit4;
45 
46 import com.android.compatibility.common.util.FileUtils;
47 
48 import org.junit.After;
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 
53 import java.io.FileOutputStream;
54 import java.io.InputStream;
55 import java.util.concurrent.CountDownLatch;
56 import java.util.concurrent.TimeUnit;
57 
58 /**
59  * Device-side tests for Bugreport Manager API
60  */
61 @RunWith(AndroidJUnit4.class)
62 public class BugreportManagerTest {
63     private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(4);
64     // Sent by Shell when bugreport finishes (contains final bugreport/screenshot file name
65     // associated to this bugreport)
66     private static final String INTENT_BUGREPORT_FINISHED =
67             "com.android.internal.intent.action.BUGREPORT_FINISHED";
68     private static final String INTENT_REMOTE_BUGREPORT_DISPATCH =
69             "android.intent.action.REMOTE_BUGREPORT_DISPATCH";
70     private static final String REMOTE_BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
71     private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
72     private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
73     private static final String BUGREPORT_SERVICE = "bugreportd";
74 
75     // TODO(b/302094358): remove this once the constant is exposed
76     private static final int BUGREPORT_MODE_ONBOARDING = 7;
77 
78     private Context mContext;
79     private Context mSystemContext;
80     private BugreportManager mBugreportManager;
81     private UiAutomation mUiAutomation;
82 
83     private boolean mIsTv;
84     private int mCurrentUserId;
85 
86     @Before
setup()87     public void setup() {
88         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
89         mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
90         mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
91         mCurrentUserId = ActivityManager.getCurrentUser();
92         mSystemContext = mContext.createContextAsUser(UserHandle.SYSTEM, 0);
93         mBugreportManager = mContext.getSystemService(BugreportManager.class);
94         mIsTv = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
95         // Kill current bugreport, so that it does not interfere with future bugreports.
96         runShellCommand("setprop ctl.stop " + BUGREPORT_SERVICE);
97     }
98 
99     @After
tearDown()100     public void tearDown() {
101         // Kill current bugreport, so that it does not interfere with future bugreports.
102         runShellCommand("setprop ctl.stop " + BUGREPORT_SERVICE);
103         mUiAutomation.dropShellPermissionIdentity();
104     }
105 
106     @Test
testBugreportParams_getMode()107     public void testBugreportParams_getMode() {
108         int expected_mode = BugreportParams.BUGREPORT_MODE_FULL;
109         BugreportParams bp = new BugreportParams(expected_mode);
110         assertThat(bp.getMode()).isEqualTo(expected_mode);
111     }
112 
113     @Test
testBugreportParams_getFlags()114     public void testBugreportParams_getFlags() {
115         {
116             BugreportParams bp = new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
117             assertThat(bp.getFlags()).isEqualTo(0);
118         }
119         {
120             BugreportParams bp = new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL,
121                     BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
122             assertThat(bp.getFlags())
123                     .isEqualTo(BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
124         }
125     }
126 
127     @LargeTest
128     @Test
129     @PlatinumTest(focusArea = "bugreport")
testTelephonyBugreport()130     public void testTelephonyBugreport() throws Exception {
131         Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_TELEPHONY);
132         String bugreport = brFiles.first;
133         String screenshot = brFiles.second;
134 
135         assertBugreportFileNameCorrect(bugreport, "-telephony-" /* suffixName */, false);
136         assertThatFileisNotEmpty(bugreport);
137         // telephony bugreport does not take any screenshot
138         assertThat(screenshot).isNull();
139     }
140 
141     @LargeTest
142     @Test
143     @PlatinumTest(focusArea = "bugreport")
testFullBugreport()144     public void testFullBugreport() throws Exception {
145         Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_FULL);
146         String bugreport = brFiles.first;
147         String screenshot = brFiles.second;
148 
149         assertBugreportFileNameCorrect(bugreport, null /* suffixName */, false);
150         assertThatFileisNotEmpty(bugreport);
151         // full bugreport takes a default screenshot
152         assertScreenshotFileNameCorrect(screenshot);
153         assertThatFileisNotEmpty(screenshot);
154     }
155 
156     @LargeTest
157     @Test
158     @PlatinumTest(focusArea = "bugreport")
testInteractiveBugreport()159     public void testInteractiveBugreport() throws Exception {
160         Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_INTERACTIVE);
161         String bugreport = brFiles.first;
162         String screenshot = brFiles.second;
163 
164         assertBugreportFileNameCorrect(bugreport, null /* suffixName */, false);
165         assertThatFileisNotEmpty(bugreport);
166         // tv does not support screenshot button in the ui, interactive bugreport takes a
167         // default screenshot.
168         if (mIsTv) {
169             assertScreenshotFileNameCorrect(screenshot);
170             assertThatFileisNotEmpty(screenshot);
171         } else {
172             assertThat(screenshot).isNull();
173         }
174     }
175 
176     @LargeTest
177     @Test
178     @PlatinumTest(focusArea = "bugreport")
testWifiBugreport()179     public void testWifiBugreport() throws Exception {
180         Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_WIFI);
181         String bugreport = brFiles.first;
182         String screenshot = brFiles.second;
183 
184         assertBugreportFileNameCorrect(bugreport, "-wifi-" /* suffixName */, false);
185         assertThatFileisNotEmpty(bugreport);
186         // wifi bugreport does not take any screenshot
187         assertThat(screenshot).isNull();
188     }
189 
190     @LargeTest
191     @Test
192     @PlatinumTest(focusArea = "bugreport")
testRemoteBugreport()193     public void testRemoteBugreport() throws Exception {
194         Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_REMOTE);
195         String bugreport = brFiles.first;
196         String screenshot = brFiles.second;
197 
198         assertBugreportFileNameCorrect(bugreport, null /* suffixName */, true);
199         assertThatFileisNotEmpty(bugreport);
200         // remote bugreport does not take any screenshot
201         assertThat(screenshot).isNull();
202     }
203 
204     @LargeTest
205     @Test
206     @PlatinumTest(focusArea = "bugreport")
testWearBugreport()207     public void testWearBugreport() throws Exception {
208         Pair<String, String> brFiles = triggerBugreport(BugreportParams.BUGREPORT_MODE_WEAR);
209         String bugreport = brFiles.first;
210         String screenshot = brFiles.second;
211 
212         assertBugreportFileNameCorrect(bugreport, null /* suffixName */, false);
213         assertThatFileisNotEmpty(bugreport);
214         // wear bugreport takes a default screenshot
215         assertScreenshotFileNameCorrect(screenshot);
216         assertThatFileisNotEmpty(screenshot);
217     }
218 
219     @LargeTest
220     @Test
221     @PlatinumTest(focusArea = "bugreport")
testOnboardingBugreport()222     public void testOnboardingBugreport() throws Exception {
223         Pair<String, String> brFiles = triggerBugreport(BUGREPORT_MODE_ONBOARDING);
224         String bugreport = brFiles.first;
225         String screenshot = brFiles.second;
226 
227         assertBugreportFileNameCorrect(bugreport, null /* suffixName */, false);
228         assertThatFileisNotEmpty(bugreport);
229         // onboarding bugreport does not take any screenshot
230         assertThat(screenshot).isNull();
231     }
232 
assertBugreportFileNameCorrect(String fileName, String suffixName, boolean isRemote)233     private void assertBugreportFileNameCorrect(String fileName, String suffixName,
234             boolean isRemote) {
235         int expectedUserId = mCurrentUserId;
236         if (isRemote) {
237             // Remote bugreport requests are sent to the SYSTEM user.
238             expectedUserId = UserHandle.SYSTEM.getIdentifier();
239         }
240         assertThat(fileName).startsWith(
241                 String.format(
242                         "/data/user_de/%d/com.android.shell/files/bugreports/bugreport-",
243                         expectedUserId));
244         assertThat(fileName).endsWith(".zip");
245         if (suffixName != null) {
246             assertThat(fileName).contains(suffixName);
247         }
248     }
249 
assertScreenshotFileNameCorrect(String fileName)250     private void assertScreenshotFileNameCorrect(String fileName) {
251         assertThat(fileName).startsWith(
252                 String.format(
253                         "/data/user_de/%d/com.android.shell/files/bugreports/screenshot-",
254                         mCurrentUserId));
255         assertThat(fileName).endsWith("-default.png");
256     }
257 
assertThatFileisNotEmpty(String file)258     private void assertThatFileisNotEmpty(String file) throws Exception {
259         // Check if the file is under "/data/user_de/0/".
260         boolean isSystemUserFile = TextUtils.equals(file.split("/")[3], "0");
261         String cmdOutput;
262         if (isSystemUserFile) {
263             cmdOutput = runShellCommand(mUiAutomation, "ls -l " + file);
264         } else {
265             // Need to run the shell command as root, to be able to access other user's files.
266             // This is needed in HSUM (Headless System User Mode), where the bugreport is generated
267             // for secondary user (e.g. user 10).
268             // TODO(b/296720745) For now skip the test for "user" build because the bugreport file
269             // cannot be accesses via shell command without running as root. We may need another way
270             // to access the file other than shell command. Note that this affects HSUM device only.
271             assumeFalse(Build.TYPE.equals("user"));
272 
273             ParcelFileDescriptor[] pfds = mUiAutomation.executeShellCommandRw("su");
274             try (FileOutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(
275                     pfds[1])) {
276                 outputStream.write(("ls -l " + file + "\n").getBytes());
277             }
278             try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfds[0])) {
279                 cmdOutput = new String(FileUtils.readInputStreamFully(inputStream));
280             }
281         }
282 
283         String[] fileInfo = cmdOutput.split(" ");
284         // Example output of ls -l: -rw------- 1 shell shell 27039619 2020-04-27 12:36 fileName.zip
285         assertThat(fileInfo.length).isEqualTo(8);
286         long fileSize = Long.parseLong(fileInfo[4]);
287         assertThat(fileSize).isGreaterThan(0L);
288     }
289 
290     private class BugreportBroadcastReceiver extends BroadcastReceiver {
291         Intent bugreportFinishedIntent = null;
292         final CountDownLatch latch;
293 
BugreportBroadcastReceiver()294         BugreportBroadcastReceiver() {
295             latch = new CountDownLatch(1);
296         }
297 
298         @Override
onReceive(Context context, Intent intent)299         public void onReceive(Context context, Intent intent) {
300             setBugreportFinishedIntent(intent);
301             latch.countDown();
302         }
303 
setBugreportFinishedIntent(Intent intent)304         private void setBugreportFinishedIntent(Intent intent) {
305             bugreportFinishedIntent = intent;
306         }
307 
getBugreportFinishedIntent()308         public Intent getBugreportFinishedIntent() {
309             return bugreportFinishedIntent;
310         }
311 
waitForBugreportFinished()312         public void waitForBugreportFinished() throws Exception {
313             if (!latch.await(BUGREPORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
314                 throw new Exception("Failed to receive BUGREPORT_FINISHED in "
315                         + BUGREPORT_TIMEOUT_MS + " ms.");
316             }
317         }
318     }
319 
triggerBugreport(int type)320     private Pair<String, String> triggerBugreport(int type) throws Exception {
321         BugreportBroadcastReceiver br = new BugreportBroadcastReceiver();
322         final IntentFilter intentFilter;
323         Context receivingContext = mContext;
324         if (type == BugreportParams.BUGREPORT_MODE_REMOTE && mCurrentUserId != 0) {
325             // Remote bugreports are handled by the system user.
326             receivingContext = mSystemContext;
327         }
328         if (type == BugreportParams.BUGREPORT_MODE_REMOTE) {
329             intentFilter = new IntentFilter(INTENT_REMOTE_BUGREPORT_DISPATCH,
330                     REMOTE_BUGREPORT_MIMETYPE);
331         } else {
332             intentFilter = new IntentFilter(INTENT_BUGREPORT_FINISHED);
333         }
334         receivingContext.registerReceiver(br, intentFilter, Context.RECEIVER_EXPORTED);
335         final BugreportParams params = new BugreportParams(type);
336         mBugreportManager.requestBugreport(params, "" /* shareTitle */, "" /* shareDescription */);
337 
338         try {
339             br.waitForBugreportFinished();
340         } finally {
341             // The latch may fail for a number of reasons but we still need to unregister the
342             // BroadcastReceiver.
343             receivingContext.unregisterReceiver(br);
344         }
345 
346         Intent response = br.getBugreportFinishedIntent();
347         assertThat(response.getAction()).isEqualTo(intentFilter.getAction(0));
348 
349         String bugreport = response.getStringExtra(EXTRA_BUGREPORT);
350         String screenshot = response.getStringExtra(EXTRA_SCREENSHOT);
351         return new Pair<String, String>(bugreport, screenshot);
352     }
353 }
354