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