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 android.os; 18 19 import android.Manifest; 20 import android.annotation.CallbackExecutor; 21 import android.annotation.FloatRange; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.annotation.SuppressAutoDoc; 27 import android.annotation.SystemApi; 28 import android.annotation.SystemService; 29 import android.annotation.UserHandleAware; 30 import android.annotation.WorkerThread; 31 import android.app.ActivityManager; 32 import android.content.Context; 33 import android.util.Log; 34 import android.widget.Toast; 35 36 import com.android.internal.R; 37 import com.android.internal.util.Preconditions; 38 39 import libcore.io.IoUtils; 40 41 import java.io.File; 42 import java.io.FileNotFoundException; 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.concurrent.Executor; 46 47 /** 48 * Class that provides a privileged API to capture and consume bugreports. 49 * 50 * <p>This class may only be used by apps that currently have carrier privileges (see {@link 51 * android.telephony.TelephonyManager#hasCarrierPrivileges}) on an active SIM or priv-apps 52 * explicitly allowed by the device manufacturer. 53 * 54 * <p>Only one bugreport can be generated by the system at a time. 55 */ 56 @SystemService(Context.BUGREPORT_SERVICE) 57 public final class BugreportManager { 58 59 private static final String TAG = "BugreportManager"; 60 61 private final Context mContext; 62 private final IDumpstate mBinder; 63 64 /** @hide */ BugreportManager(@onNull Context context, IDumpstate binder)65 public BugreportManager(@NonNull Context context, IDumpstate binder) { 66 mContext = context; 67 mBinder = binder; 68 } 69 70 /** 71 * An interface describing the callback for bugreport progress and status. 72 * 73 * <p>Callers will receive {@link #onProgress} calls as the bugreport progresses, followed by a 74 * terminal call to either {@link #onFinished} or {@link #onError}. 75 * 76 * <p>If an issue is encountered while starting the bugreport asynchronously, callers will 77 * receive an {@link #onError} call without any {@link #onProgress} callbacks. 78 */ 79 public abstract static class BugreportCallback { 80 /** 81 * Possible error codes taking a bugreport can encounter. 82 * 83 * @hide 84 */ 85 @Retention(RetentionPolicy.SOURCE) 86 @IntDef( 87 prefix = {"BUGREPORT_ERROR_"}, 88 value = { 89 BUGREPORT_ERROR_INVALID_INPUT, 90 BUGREPORT_ERROR_RUNTIME, 91 BUGREPORT_ERROR_USER_DENIED_CONSENT, 92 BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT, 93 BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS, 94 BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE 95 }) 96 public @interface BugreportErrorCode {} 97 98 /** 99 * The input options were invalid. For example, the destination file the app provided could 100 * not be written by the system. 101 */ 102 public static final int BUGREPORT_ERROR_INVALID_INPUT = 103 IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; 104 105 /** A runtime error occurred. */ 106 public static final int BUGREPORT_ERROR_RUNTIME = 107 IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; 108 109 /** User denied consent to share the bugreport. */ 110 public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 111 IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; 112 113 /** The request to get user consent timed out. */ 114 public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 115 IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT; 116 117 /** There is currently a bugreport running. The caller should try again later. */ 118 public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 119 IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS; 120 121 /** There is no bugreport to retrieve for the caller. */ 122 public static final int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE = 123 IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE; 124 125 /** 126 * Called when there is a progress update. 127 * 128 * @param progress the progress in [0.0, 100.0] 129 */ onProgress(@loatRangefrom = 0f, to = 100f) float progress)130 public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {} 131 132 /** 133 * Called when taking bugreport resulted in an error. 134 * 135 * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not 136 * consent to sharing the bugreport with the calling app. 137 * 138 * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed 139 * out, but the bugreport could be available in the internal directory of dumpstate for 140 * manual retrieval. 141 * 142 * <p>If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the caller 143 * should try later, as only one bugreport can be in progress at a time. 144 */ onError(@ugreportErrorCode int errorCode)145 public void onError(@BugreportErrorCode int errorCode) {} 146 147 /** 148 * Called when taking bugreport finishes successfully. 149 * 150 * <p>This callback will be invoked if the 151 * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set. 152 */ onFinished()153 public void onFinished() {} 154 155 /** Called when taking bugreport finishes successfully. 156 * 157 * <p>This callback will only be invoked if the 158 * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is set. Otherwise, the 159 * {@link #onFinished()} callback will be invoked. 160 * 161 * @param bugreportFile the absolute path of the generated bugreport file. 162 * @hide 163 164 */ 165 @SystemApi onFinished(@onNull String bugreportFile)166 public void onFinished(@NonNull String bugreportFile) {} 167 168 /** 169 * Called when it is ready for calling app to show UI, showing any extra UI before this 170 * callback can interfere with bugreport generation. 171 */ onEarlyReportFinished()172 public void onEarlyReportFinished() {} 173 } 174 175 /** 176 * Speculatively pre-dumps UI data for a bugreport request that might come later. 177 * 178 * <p>Triggers the dump of certain critical UI data, e.g. traces stored in short 179 * ring buffers that might get lost by the time the actual bugreport is requested. 180 * 181 * <p>{@link #startBugreport} will then pick the pre-dumped data if both of the following 182 * conditions are met: 183 * - {@link android.os.BugreportParams#BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified. 184 * - {@link #preDumpUiData} and {@link #startBugreport} were called by the same UID. 185 * @hide 186 */ 187 @SystemApi 188 @RequiresPermission(android.Manifest.permission.DUMP) 189 @WorkerThread preDumpUiData()190 public void preDumpUiData() { 191 try { 192 mBinder.preDumpUiData(mContext.getOpPackageName()); 193 } catch (RemoteException e) { 194 throw e.rethrowFromSystemServer(); 195 } 196 } 197 198 /** 199 * Starts a bugreport. 200 * 201 * <p>This starts a bugreport in the background. However the call itself can take several 202 * seconds to return in the worst case. {@code callback} will receive progress and status 203 * updates. 204 * 205 * <p>The bugreport artifacts will be copied over to the given file descriptors only if the user 206 * consents to sharing with the calling app. If 207 * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} is set, user consent will be deferred 208 * and no files will be copied to the given file descriptors. 209 * 210 * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}. 211 * 212 * @param bugreportFd file to write the bugreport. This should be opened in write-only, append 213 * mode. 214 * @param screenshotFd file to write the screenshot, if necessary. This should be opened in 215 * write-only, append mode. 216 * @param params options that specify what kind of a bugreport should be taken 217 * @param callback callback for progress and status updates 218 * @hide 219 */ 220 @SystemApi 221 @RequiresPermission(android.Manifest.permission.DUMP) 222 @WorkerThread startBugreport( @onNull ParcelFileDescriptor bugreportFd, @Nullable ParcelFileDescriptor screenshotFd, @NonNull BugreportParams params, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback)223 public void startBugreport( 224 @NonNull ParcelFileDescriptor bugreportFd, 225 @Nullable ParcelFileDescriptor screenshotFd, 226 @NonNull BugreportParams params, 227 @NonNull @CallbackExecutor Executor executor, 228 @NonNull BugreportCallback callback) { 229 try { 230 Preconditions.checkNotNull(bugreportFd); 231 Preconditions.checkNotNull(params); 232 Preconditions.checkNotNull(executor); 233 Preconditions.checkNotNull(callback); 234 235 boolean deferConsent = 236 (params.getFlags() & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0; 237 boolean isScreenshotRequested = screenshotFd != null || deferConsent; 238 if (screenshotFd == null) { 239 // Binder needs a valid File Descriptor to be passed 240 screenshotFd = 241 ParcelFileDescriptor.open( 242 new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY); 243 } 244 DumpstateListener dsListener = 245 new DumpstateListener(executor, callback, isScreenshotRequested, deferConsent); 246 // Note: mBinder can get callingUid from the binder transaction. 247 mBinder.startBugreport( 248 -1 /* callingUid */, 249 mContext.getOpPackageName(), 250 bugreportFd.getFileDescriptor(), 251 screenshotFd.getFileDescriptor(), 252 params.getMode(), 253 params.getFlags(), 254 dsListener, 255 isScreenshotRequested, 256 /* skipUserConsent = */ false); 257 } catch (RemoteException e) { 258 throw e.rethrowFromSystemServer(); 259 } catch (FileNotFoundException e) { 260 Log.wtf(TAG, "Not able to find /dev/null file: ", e); 261 } finally { 262 // We can close the file descriptors here because binder would have duped them. 263 IoUtils.closeQuietly(bugreportFd); 264 if (screenshotFd != null) { 265 IoUtils.closeQuietly(screenshotFd); 266 } 267 } 268 } 269 270 /** 271 * Retrieves a previously generated bugreport. 272 * 273 * <p>The previously generated bugreport must have been generated by calling {@link 274 * #startBugreport(ParcelFileDescriptor, ParcelFileDescriptor, BugreportParams, 275 * Executor, BugreportCallback)} with the {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} 276 * flag set. The bugreport file returned by the {@link BugreportCallback#onFinished(String)} 277 * callback for a previously generated bugreport must be passed to this method. A caller may 278 * only retrieve bugreports that they have previously requested. 279 * 280 * <p>The bugreport artifacts will be copied over to the given file descriptor only if the user 281 * consents to sharing with the calling app. 282 * 283 * <p>{@link BugreportManager} takes ownership of {@code bugreportFd}. 284 * 285 * <p>The caller can reattempt to retrieve the bugreport multiple times if the user has not 286 * consented on previous attempts. 287 * 288 * @param bugreportFile the identifier for a bugreport that was previously generated for this 289 * caller using {@code startBugreport}. 290 * @param bugreportFd file to copy over the previous bugreport. This should be opened in 291 * write-only, append mode. 292 * @param executor the executor to execute callback methods. 293 * @param callback callback for progress and status updates. 294 * @hide 295 */ 296 @SystemApi 297 @RequiresPermission(Manifest.permission.DUMP) 298 @WorkerThread 299 @UserHandleAware retrieveBugreport( @onNull String bugreportFile, @NonNull ParcelFileDescriptor bugreportFd, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback )300 public void retrieveBugreport( 301 @NonNull String bugreportFile, 302 @NonNull ParcelFileDescriptor bugreportFd, 303 @NonNull @CallbackExecutor Executor executor, 304 @NonNull BugreportCallback callback 305 ) { 306 try { 307 Preconditions.checkNotNull(bugreportFile); 308 Preconditions.checkNotNull(bugreportFd); 309 Preconditions.checkNotNull(executor); 310 Preconditions.checkNotNull(callback); 311 DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false); 312 mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(), 313 mContext.getUserId(), 314 bugreportFd.getFileDescriptor(), 315 bugreportFile, 316 /* keepBugreportOnRetrieval = */ false, 317 /* skipUserConsent = */ false, 318 dsListener); 319 } catch (RemoteException e) { 320 throw e.rethrowFromSystemServer(); 321 } finally { 322 IoUtils.closeQuietly(bugreportFd); 323 } 324 } 325 326 /** 327 * Starts a connectivity bugreport. 328 * 329 * <p>The connectivity bugreport is a specialized version of bugreport that only includes 330 * information specifically for debugging connectivity-related issues (e.g. telephony, wi-fi, 331 * and IP networking issues). It is intended primarily for use by OEMs and network providers 332 * such as mobile network operators. In addition to generally excluding information that isn't 333 * targeted to connectivity debugging, this type of bugreport excludes PII and sensitive 334 * information that isn't strictly necessary for connectivity debugging. 335 * 336 * <p>The calling app MUST have a context-specific reason for requesting a connectivity 337 * bugreport, such as detecting a connectivity-related issue. This API SHALL NOT be used to 338 * perform random sampling from a fleet of public end-user devices. 339 * 340 * <p>Calling this API will cause the system to ask the user for consent every single time. The 341 * bugreport artifacts will be copied over to the given file descriptors only if the user 342 * consents to sharing with the calling app. 343 * 344 * <p>This starts a bugreport in the background. However the call itself can take several 345 * seconds to return in the worst case. {@code callback} will receive progress and status 346 * updates. 347 * 348 * <p>Requires that the calling app has carrier privileges (see {@link 349 * android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription. 350 * 351 * @param bugreportFd file to write the bugreport. This should be opened in write-only, append 352 * mode. 353 * @param callback callback for progress and status updates. 354 */ 355 @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges 356 @WorkerThread startConnectivityBugreport( @onNull ParcelFileDescriptor bugreportFd, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback)357 public void startConnectivityBugreport( 358 @NonNull ParcelFileDescriptor bugreportFd, 359 @NonNull @CallbackExecutor Executor executor, 360 @NonNull BugreportCallback callback) { 361 startBugreport( 362 bugreportFd, 363 null /* screenshotFd */, 364 new BugreportParams(BugreportParams.BUGREPORT_MODE_TELEPHONY), 365 executor, 366 callback); 367 } 368 369 /** 370 * Cancels the currently running bugreport. 371 * 372 * <p>Apps are only able to cancel their own bugreports. App A cannot cancel a bugreport started 373 * by app B. 374 * 375 * <p>Requires permission: {@link android.Manifest.permission#DUMP} or that the calling app has 376 * carrier privileges (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on 377 * any active subscription. 378 * 379 * @throws SecurityException if trying to cancel another app's bugreport in progress 380 */ 381 @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges 382 @WorkerThread cancelBugreport()383 public void cancelBugreport() { 384 try { 385 mBinder.cancelBugreport(-1 /* callingUid */, mContext.getOpPackageName()); 386 } catch (RemoteException e) { 387 throw e.rethrowFromSystemServer(); 388 } 389 } 390 391 /** 392 * Requests a bugreport. 393 * 394 * <p>This requests the platform/system to take a bugreport and makes the final bugreport 395 * available to the user. The user may choose to share it with another app, but the bugreport is 396 * never given back directly to the app that requested it. 397 * 398 * @param params {@link BugreportParams} that specify what kind of a bugreport should be taken, 399 * please note that not all kinds of bugreport allow for a progress notification 400 * @param shareTitle title on the final share notification 401 * @param shareDescription description on the final share notification 402 * @hide 403 */ 404 @SystemApi 405 @RequiresPermission(Manifest.permission.DUMP) requestBugreport( @onNull BugreportParams params, @Nullable CharSequence shareTitle, @Nullable CharSequence shareDescription)406 public void requestBugreport( 407 @NonNull BugreportParams params, 408 @Nullable CharSequence shareTitle, 409 @Nullable CharSequence shareDescription) { 410 try { 411 String title = shareTitle == null ? null : shareTitle.toString(); 412 String description = shareDescription == null ? null : shareDescription.toString(); 413 ActivityManager.getService() 414 .requestBugReportWithDescription(title, description, params.getMode()); 415 } catch (RemoteException e) { 416 throw e.rethrowFromSystemServer(); 417 } 418 } 419 420 private final class DumpstateListener extends IDumpstateListener.Stub { 421 private final Executor mExecutor; 422 private final BugreportCallback mCallback; 423 private final boolean mIsScreenshotRequested; 424 private final boolean mIsConsentDeferred; 425 DumpstateListener( Executor executor, BugreportCallback callback, boolean isScreenshotRequested, boolean isConsentDeferred)426 DumpstateListener( 427 Executor executor, BugreportCallback callback, boolean isScreenshotRequested, 428 boolean isConsentDeferred) { 429 mExecutor = executor; 430 mCallback = callback; 431 mIsScreenshotRequested = isScreenshotRequested; 432 mIsConsentDeferred = isConsentDeferred; 433 } 434 435 @Override onProgress(int progress)436 public void onProgress(int progress) throws RemoteException { 437 final long identity = Binder.clearCallingIdentity(); 438 try { 439 mExecutor.execute(() -> mCallback.onProgress(progress)); 440 } finally { 441 Binder.restoreCallingIdentity(identity); 442 } 443 } 444 445 @Override onError(int errorCode)446 public void onError(int errorCode) throws RemoteException { 447 final long identity = Binder.clearCallingIdentity(); 448 try { 449 mExecutor.execute(() -> mCallback.onError(errorCode)); 450 } finally { 451 Binder.restoreCallingIdentity(identity); 452 } 453 } 454 455 @Override onFinished(String bugreportFile)456 public void onFinished(String bugreportFile) throws RemoteException { 457 final long identity = Binder.clearCallingIdentity(); 458 try { 459 if (mIsConsentDeferred) { 460 mExecutor.execute(() -> mCallback.onFinished(bugreportFile)); 461 } else { 462 mExecutor.execute(() -> mCallback.onFinished()); 463 } 464 } finally { 465 Binder.restoreCallingIdentity(identity); 466 } 467 } 468 469 @Override onScreenshotTaken(boolean success)470 public void onScreenshotTaken(boolean success) throws RemoteException { 471 if (!mIsScreenshotRequested) { 472 return; 473 } 474 475 Handler mainThreadHandler = new Handler(Looper.getMainLooper()); 476 mainThreadHandler.post( 477 () -> { 478 int message = 479 success 480 ? R.string.bugreport_screenshot_success_toast 481 : R.string.bugreport_screenshot_failure_toast; 482 Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); 483 }); 484 } 485 486 @Override onUiIntensiveBugreportDumpsFinished()487 public void onUiIntensiveBugreportDumpsFinished() throws RemoteException { 488 final long identity = Binder.clearCallingIdentity(); 489 try { 490 mExecutor.execute(() -> mCallback.onEarlyReportFinished()); 491 } finally { 492 Binder.restoreCallingIdentity(identity); 493 } 494 } 495 } 496 } 497