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