Loading core/api/current.txt +19 −0 Original line number Diff line number Diff line Loading @@ -10350,6 +10350,7 @@ package android.content { field public static final String BIOMETRIC_SERVICE = "biometric"; field public static final String BLOB_STORE_SERVICE = "blob_store"; field public static final String BLUETOOTH_SERVICE = "bluetooth"; field public static final String BUGREPORT_SERVICE = "bugreport"; field public static final String CAMERA_SERVICE = "camera"; field public static final String CAPTIONING_SERVICE = "captioning"; field public static final String CARRIER_CONFIG_SERVICE = "carrier_config"; Loading Loading @@ -30081,6 +30082,24 @@ package android.os { method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int); } public final class BugreportManager { method public void cancelBugreport(); method public void startConnectivityBugreport(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); } public abstract static class BugreportManager.BugreportCallback { ctor public BugreportManager.BugreportCallback(); method public void onEarlyReportFinished(); method public void onError(int); method public void onFinished(); method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float); field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5 field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1 field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2 field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4 field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3 } public class Build { ctor public Build(); method @NonNull public static java.util.List<android.os.Build.Partition> getFingerprintedPartitions(); core/api/system-current.txt +0 −15 Original line number Diff line number Diff line Loading @@ -1922,7 +1922,6 @@ package android.content { field public static final String BATTERY_STATS_SERVICE = "batterystats"; field public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000 field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000 field public static final String BUGREPORT_SERVICE = "bugreport"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; field public static final String ETHERNET_SERVICE = "ethernet"; Loading Loading @@ -7852,24 +7851,10 @@ package android.os { } public final class BugreportManager { method @RequiresPermission(android.Manifest.permission.DUMP) public void cancelBugreport(); method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence); method @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); } public abstract static class BugreportManager.BugreportCallback { ctor public BugreportManager.BugreportCallback(); method public void onEarlyReportFinished(); method public void onError(int); method public void onFinished(); method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float); field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5 field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1 field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2 field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4 field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3 } public final class BugreportParams { ctor public BugreportParams(int); method public int getMode(); core/java/android/content/Context.java +0 −2 Original line number Diff line number Diff line Loading @@ -5086,9 +5086,7 @@ public abstract class Context { * Service to capture a bugreport. * @see #getSystemService(String) * @see android.os.BugreportManager * @hide */ @SystemApi public static final String BUGREPORT_SERVICE = "bugreport"; /** Loading core/java/android/os/BugreportManager.java +112 −66 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.ActivityManager; Loading @@ -40,12 +41,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; /** * Class that provides a privileged API to capture and consume bugreports. * * @hide */ @SystemApi /** Class that provides a privileged API to capture and consume bugreports. */ @SystemService(Context.BUGREPORT_SERVICE) public final class BugreportManager { Loading @@ -60,28 +56,30 @@ public final class BugreportManager { mBinder = binder; } /** An interface describing the callback for bugreport progress and status. */ public abstract static class BugreportCallback { /** * An interface describing the callback for bugreport progress and status. * Possible error codes taking a bugreport can encounter. * * @hide */ public abstract static class BugreportCallback { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { @IntDef( prefix = {"BUGREPORT_ERROR_"}, value = { BUGREPORT_ERROR_INVALID_INPUT, BUGREPORT_ERROR_RUNTIME, BUGREPORT_ERROR_USER_DENIED_CONSENT, BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT, BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS }) /** Possible error codes taking a bugreport can encounter */ public @interface BugreportErrorCode {} /** The input options were invalid */ public static final int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; /** A runtime error occured */ /** A runtime error occurred */ public static final int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; Loading @@ -99,6 +97,7 @@ public final class BugreportManager { /** * Called when there is a progress update. * * @param progress the progress in [0.0, 100.0] */ public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {} Loading @@ -113,14 +112,12 @@ public final class BugreportManager { * out, but the bugreport could be available in the internal directory of dumpstate for * manual retrieval. * * <p> If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the * caller should try later, as only one bugreport can be in progress at a time. * <p>If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the caller * should try later, as only one bugreport can be in progress at a time. */ public void onError(@BugreportErrorCode int errorCode) {} /** * Called when taking bugreport finishes successfully. */ /** Called when taking bugreport finishes successfully. */ public void onFinished() {} /** Loading @@ -137,20 +134,23 @@ public final class BugreportManager { * seconds to return in the worst case. {@code callback} will receive progress and status * updates. * * <p>The bugreport artifacts will be copied over to the given file descriptors only if the * user consents to sharing with the calling app. * <p>The bugreport artifacts will be copied over to the given file descriptors only if the user * consents to sharing with the calling app. * * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}. * * @param bugreportFd file to write the bugreport. This should be opened in write-only, * append mode. * @param screenshotFd file to write the screenshot, if necessary. This should be opened * in write-only, append mode. * @param bugreportFd file to write the bugreport. This should be opened in write-only, append * mode. * @param screenshotFd file to write the screenshot, if necessary. This should be opened in * write-only, append mode. * @param params options that specify what kind of a bugreport should be taken * @param callback callback for progress and status updates * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd, public void startBugreport( @NonNull ParcelFileDescriptor bugreportFd, @Nullable ParcelFileDescriptor screenshotFd, @NonNull BugreportParams params, @NonNull @CallbackExecutor Executor executor, Loading @@ -164,17 +164,21 @@ public final class BugreportManager { boolean isScreenshotRequested = screenshotFd != null; if (screenshotFd == null) { // Binder needs a valid File Descriptor to be passed screenshotFd = ParcelFileDescriptor.open(new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY); screenshotFd = ParcelFileDescriptor.open( new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY); } DumpstateListener dsListener = new DumpstateListener(executor, callback, isScreenshotRequested); DumpstateListener dsListener = new DumpstateListener(executor, callback, isScreenshotRequested); // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, mBinder.startBugreport( -1 /* callingUid */, mContext.getOpPackageName(), bugreportFd.getFileDescriptor(), screenshotFd.getFileDescriptor(), params.getMode(), dsListener, isScreenshotRequested); params.getMode(), dsListener, isScreenshotRequested); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (FileNotFoundException e) { Loading @@ -188,15 +192,61 @@ public final class BugreportManager { } } /** * Starts a connectivity bugreport. * * <p>The connectivity bugreport is a specialized version of bugreport that only includes * information specifically for debugging connectivity-related issues (e.g. telephony, wi-fi, * and IP networking issues). It is intended primarily for use by OEMs and network providers * such as mobile network operators. In addition to generally excluding information that isn't * targeted to connectivity debugging, this type of bugreport excludes PII and sensitive * information that isn't strictly necessary for connectivity debugging. * * <p>The calling app MUST have a context-specific reason for requesting a connectivity * bugreport, such as detecting a connectivity-related issue. This API SHALL NOT be used to * perform random sampling from a fleet of public end-user devices. * * <p>Calling this API will cause the system to ask the user for consent every single time. The * bugreport artifacts will be copied over to the given file descriptors only if the user * consents to sharing with the calling app. * * <p>This starts a bugreport in the background. However the call itself can take several * seconds to return in the worst case. {@code callback} will receive progress and status * updates. * * <p>Requires that the calling app has carrier privileges (see {@link * android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription. * * @param bugreportFd file to write the bugreport. This should be opened in write-only, append * mode. * @param callback callback for progress and status updates. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges public void startConnectivityBugreport( @NonNull ParcelFileDescriptor bugreportFd, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback) { startBugreport( bugreportFd, null /* screenshotFd */, new BugreportParams(BugreportParams.BUGREPORT_MODE_TELEPHONY), executor, callback); } /** * Cancels the currently running bugreport. * * <p>Apps are only able to cancel their own bugreports. App A cannot cancel a bugreport started * by app B. * * <p>Requires permission: {@link android.Manifest.permission#DUMP} or that the calling app has * carrier privileges (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on * any active subscription. * * @throws SecurityException if trying to cancel another app's bugreport in progress */ @RequiresPermission(android.Manifest.permission.DUMP) @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges public void cancelBugreport() { try { mBinder.cancelBugreport(-1 /* callingUid */, mContext.getOpPackageName()); Loading @@ -209,23 +259,26 @@ public final class BugreportManager { * Requests a bugreport. * * <p>This requests the platform/system to take a bugreport and makes the final bugreport * available to the user. The user may choose to share it with another app, but the bugreport * is never given back directly to the app that requested it. * available to the user. The user may choose to share it with another app, but the bugreport is * never given back directly to the app that requested it. * * @param params {@link BugreportParams} that specify what kind of a bugreport should * be taken, please note that not all kinds of bugreport allow for a * progress notification * @param params {@link BugreportParams} that specify what kind of a bugreport should be taken, * please note that not all kinds of bugreport allow for a progress notification * @param shareTitle title on the final share notification * @param shareDescription description on the final share notification * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull BugreportParams params, @Nullable CharSequence shareTitle, public void requestBugreport( @NonNull BugreportParams params, @Nullable CharSequence shareTitle, @Nullable CharSequence shareDescription) { try { String title = shareTitle == null ? null : shareTitle.toString(); String description = shareDescription == null ? null : shareDescription.toString(); ActivityManager.getService().requestBugReportWithDescription(title, description, params.getMode()); ActivityManager.getService() .requestBugReportWithDescription(title, description, params.getMode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading @@ -236,8 +289,8 @@ public final class BugreportManager { private final BugreportCallback mCallback; private final boolean mIsScreenshotRequested; DumpstateListener(Executor executor, BugreportCallback callback, boolean isScreenshotRequested) { DumpstateListener( Executor executor, BugreportCallback callback, boolean isScreenshotRequested) { mExecutor = executor; mCallback = callback; mIsScreenshotRequested = isScreenshotRequested; Loading @@ -247,9 +300,7 @@ public final class BugreportManager { public void onProgress(int progress) throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onProgress(progress); }); mExecutor.execute(() -> mCallback.onProgress(progress)); } finally { Binder.restoreCallingIdentity(identity); } Loading @@ -259,9 +310,7 @@ public final class BugreportManager { public void onError(int errorCode) throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onError(errorCode); }); mExecutor.execute(() -> mCallback.onError(errorCode)); } finally { Binder.restoreCallingIdentity(identity); } Loading @@ -271,9 +320,7 @@ public final class BugreportManager { public void onFinished() throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onFinished(); }); mExecutor.execute(() -> mCallback.onFinished()); } finally { Binder.restoreCallingIdentity(identity); } Loading @@ -288,20 +335,19 @@ public final class BugreportManager { Handler mainThreadHandler = new Handler(Looper.getMainLooper()); mainThreadHandler.post( () -> { int message = success ? R.string.bugreport_screenshot_success_toast int message = success ? R.string.bugreport_screenshot_success_toast : R.string.bugreport_screenshot_failure_toast; Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); }); } @Override public void onUiIntensiveBugreportDumpsFinished() throws RemoteException { public void onUiIntensiveBugreportDumpsFinished() throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onEarlyReportFinished(); }); mExecutor.execute(() -> mCallback.onEarlyReportFinished()); } finally { Binder.restoreCallingIdentity(identity); } Loading services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +39 −13 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Binder; import android.os.BugreportParams; Loading @@ -31,6 +32,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserManager; import android.telephony.TelephonyManager; import android.util.ArraySet; import android.util.Slog; Loading @@ -53,11 +55,13 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private final Object mLock = new Object(); private final Context mContext; private final AppOpsManager mAppOps; private final TelephonyManager mTelephonyManager; private final ArraySet<String> mBugreportWhitelistedPackages; BugreportManagerServiceImpl(Context context) { mContext = context; mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mAppOps = context.getSystemService(AppOpsManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mBugreportWhitelistedPackages = SystemConfig.getInstance().getBugreportWhitelistedPackages(); } Loading @@ -67,11 +71,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { public void startBugreport(int callingUidUnused, String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport"); Objects.requireNonNull(callingPackage); Objects.requireNonNull(bugreportFd); Objects.requireNonNull(listener); validateBugreportMode(bugreportMode); int callingUid = Binder.getCallingUid(); enforcePermission(callingPackage, callingUid, bugreportMode == BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */); final long identity = Binder.clearCallingIdentity(); try { ensureIsPrimaryUser(); Loading @@ -79,13 +86,6 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { Binder.restoreCallingIdentity(identity); } int callingUid = Binder.getCallingUid(); mAppOps.checkPackage(callingUid, callingPackage); if (!mBugreportWhitelistedPackages.contains(callingPackage)) { throw new SecurityException( callingPackage + " is not whitelisted to use Bugreport API"); } synchronized (mLock) { startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode, listener, isScreenshotRequested); Loading @@ -93,12 +93,10 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } @Override @RequiresPermission(android.Manifest.permission.DUMP) @RequiresPermission(android.Manifest.permission.DUMP) // or carrier privileges public void cancelBugreport(int callingUidUnused, String callingPackage) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "cancelBugreport"); int callingUid = Binder.getCallingUid(); mAppOps.checkPackage(callingUid, callingPackage); enforcePermission(callingPackage, callingUid, true /* checkCarrierPrivileges */); synchronized (mLock) { IDumpstate ds = getDumpstateBinderServiceLocked(); Loading Loading @@ -134,6 +132,34 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } } private void enforcePermission( String callingPackage, int callingUid, boolean checkCarrierPrivileges) { mAppOps.checkPackage(callingUid, callingPackage); // To gain access through the DUMP permission, the OEM has to allow this package explicitly // via sysconfig and privileged permissions. if (mBugreportWhitelistedPackages.contains(callingPackage) && mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) == PackageManager.PERMISSION_GRANTED) { return; } // For carrier privileges, this can include user-installed apps. This is essentially a // function of the current active SIM(s) in the device to let carrier apps through. if (checkCarrierPrivileges && mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { return; } String message = callingPackage + " does not hold the DUMP permission or is not bugreport-whitelisted " + (checkCarrierPrivileges ? "and does not have carrier privileges " : "") + "to request a bugreport"; Slog.w(TAG, message); throw new SecurityException(message); } /** * Validates that the current user is the primary user. * Loading Loading
core/api/current.txt +19 −0 Original line number Diff line number Diff line Loading @@ -10350,6 +10350,7 @@ package android.content { field public static final String BIOMETRIC_SERVICE = "biometric"; field public static final String BLOB_STORE_SERVICE = "blob_store"; field public static final String BLUETOOTH_SERVICE = "bluetooth"; field public static final String BUGREPORT_SERVICE = "bugreport"; field public static final String CAMERA_SERVICE = "camera"; field public static final String CAPTIONING_SERVICE = "captioning"; field public static final String CARRIER_CONFIG_SERVICE = "carrier_config"; Loading Loading @@ -30081,6 +30082,24 @@ package android.os { method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int); } public final class BugreportManager { method public void cancelBugreport(); method public void startConnectivityBugreport(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); } public abstract static class BugreportManager.BugreportCallback { ctor public BugreportManager.BugreportCallback(); method public void onEarlyReportFinished(); method public void onError(int); method public void onFinished(); method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float); field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5 field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1 field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2 field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4 field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3 } public class Build { ctor public Build(); method @NonNull public static java.util.List<android.os.Build.Partition> getFingerprintedPartitions();
core/api/system-current.txt +0 −15 Original line number Diff line number Diff line Loading @@ -1922,7 +1922,6 @@ package android.content { field public static final String BATTERY_STATS_SERVICE = "batterystats"; field public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000 field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000 field public static final String BUGREPORT_SERVICE = "bugreport"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; field public static final String ETHERNET_SERVICE = "ethernet"; Loading Loading @@ -7852,24 +7851,10 @@ package android.os { } public final class BugreportManager { method @RequiresPermission(android.Manifest.permission.DUMP) public void cancelBugreport(); method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence); method @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); } public abstract static class BugreportManager.BugreportCallback { ctor public BugreportManager.BugreportCallback(); method public void onEarlyReportFinished(); method public void onError(int); method public void onFinished(); method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float); field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5 field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1 field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2 field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4 field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3 } public final class BugreportParams { ctor public BugreportParams(int); method public int getMode();
core/java/android/content/Context.java +0 −2 Original line number Diff line number Diff line Loading @@ -5086,9 +5086,7 @@ public abstract class Context { * Service to capture a bugreport. * @see #getSystemService(String) * @see android.os.BugreportManager * @hide */ @SystemApi public static final String BUGREPORT_SERVICE = "bugreport"; /** Loading
core/java/android/os/BugreportManager.java +112 −66 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.ActivityManager; Loading @@ -40,12 +41,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; /** * Class that provides a privileged API to capture and consume bugreports. * * @hide */ @SystemApi /** Class that provides a privileged API to capture and consume bugreports. */ @SystemService(Context.BUGREPORT_SERVICE) public final class BugreportManager { Loading @@ -60,28 +56,30 @@ public final class BugreportManager { mBinder = binder; } /** An interface describing the callback for bugreport progress and status. */ public abstract static class BugreportCallback { /** * An interface describing the callback for bugreport progress and status. * Possible error codes taking a bugreport can encounter. * * @hide */ public abstract static class BugreportCallback { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { @IntDef( prefix = {"BUGREPORT_ERROR_"}, value = { BUGREPORT_ERROR_INVALID_INPUT, BUGREPORT_ERROR_RUNTIME, BUGREPORT_ERROR_USER_DENIED_CONSENT, BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT, BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS }) /** Possible error codes taking a bugreport can encounter */ public @interface BugreportErrorCode {} /** The input options were invalid */ public static final int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; /** A runtime error occured */ /** A runtime error occurred */ public static final int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; Loading @@ -99,6 +97,7 @@ public final class BugreportManager { /** * Called when there is a progress update. * * @param progress the progress in [0.0, 100.0] */ public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {} Loading @@ -113,14 +112,12 @@ public final class BugreportManager { * out, but the bugreport could be available in the internal directory of dumpstate for * manual retrieval. * * <p> If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the * caller should try later, as only one bugreport can be in progress at a time. * <p>If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the caller * should try later, as only one bugreport can be in progress at a time. */ public void onError(@BugreportErrorCode int errorCode) {} /** * Called when taking bugreport finishes successfully. */ /** Called when taking bugreport finishes successfully. */ public void onFinished() {} /** Loading @@ -137,20 +134,23 @@ public final class BugreportManager { * seconds to return in the worst case. {@code callback} will receive progress and status * updates. * * <p>The bugreport artifacts will be copied over to the given file descriptors only if the * user consents to sharing with the calling app. * <p>The bugreport artifacts will be copied over to the given file descriptors only if the user * consents to sharing with the calling app. * * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}. * * @param bugreportFd file to write the bugreport. This should be opened in write-only, * append mode. * @param screenshotFd file to write the screenshot, if necessary. This should be opened * in write-only, append mode. * @param bugreportFd file to write the bugreport. This should be opened in write-only, append * mode. * @param screenshotFd file to write the screenshot, if necessary. This should be opened in * write-only, append mode. * @param params options that specify what kind of a bugreport should be taken * @param callback callback for progress and status updates * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd, public void startBugreport( @NonNull ParcelFileDescriptor bugreportFd, @Nullable ParcelFileDescriptor screenshotFd, @NonNull BugreportParams params, @NonNull @CallbackExecutor Executor executor, Loading @@ -164,17 +164,21 @@ public final class BugreportManager { boolean isScreenshotRequested = screenshotFd != null; if (screenshotFd == null) { // Binder needs a valid File Descriptor to be passed screenshotFd = ParcelFileDescriptor.open(new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY); screenshotFd = ParcelFileDescriptor.open( new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY); } DumpstateListener dsListener = new DumpstateListener(executor, callback, isScreenshotRequested); DumpstateListener dsListener = new DumpstateListener(executor, callback, isScreenshotRequested); // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, mBinder.startBugreport( -1 /* callingUid */, mContext.getOpPackageName(), bugreportFd.getFileDescriptor(), screenshotFd.getFileDescriptor(), params.getMode(), dsListener, isScreenshotRequested); params.getMode(), dsListener, isScreenshotRequested); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (FileNotFoundException e) { Loading @@ -188,15 +192,61 @@ public final class BugreportManager { } } /** * Starts a connectivity bugreport. * * <p>The connectivity bugreport is a specialized version of bugreport that only includes * information specifically for debugging connectivity-related issues (e.g. telephony, wi-fi, * and IP networking issues). It is intended primarily for use by OEMs and network providers * such as mobile network operators. In addition to generally excluding information that isn't * targeted to connectivity debugging, this type of bugreport excludes PII and sensitive * information that isn't strictly necessary for connectivity debugging. * * <p>The calling app MUST have a context-specific reason for requesting a connectivity * bugreport, such as detecting a connectivity-related issue. This API SHALL NOT be used to * perform random sampling from a fleet of public end-user devices. * * <p>Calling this API will cause the system to ask the user for consent every single time. The * bugreport artifacts will be copied over to the given file descriptors only if the user * consents to sharing with the calling app. * * <p>This starts a bugreport in the background. However the call itself can take several * seconds to return in the worst case. {@code callback} will receive progress and status * updates. * * <p>Requires that the calling app has carrier privileges (see {@link * android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription. * * @param bugreportFd file to write the bugreport. This should be opened in write-only, append * mode. * @param callback callback for progress and status updates. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges public void startConnectivityBugreport( @NonNull ParcelFileDescriptor bugreportFd, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback) { startBugreport( bugreportFd, null /* screenshotFd */, new BugreportParams(BugreportParams.BUGREPORT_MODE_TELEPHONY), executor, callback); } /** * Cancels the currently running bugreport. * * <p>Apps are only able to cancel their own bugreports. App A cannot cancel a bugreport started * by app B. * * <p>Requires permission: {@link android.Manifest.permission#DUMP} or that the calling app has * carrier privileges (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on * any active subscription. * * @throws SecurityException if trying to cancel another app's bugreport in progress */ @RequiresPermission(android.Manifest.permission.DUMP) @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges public void cancelBugreport() { try { mBinder.cancelBugreport(-1 /* callingUid */, mContext.getOpPackageName()); Loading @@ -209,23 +259,26 @@ public final class BugreportManager { * Requests a bugreport. * * <p>This requests the platform/system to take a bugreport and makes the final bugreport * available to the user. The user may choose to share it with another app, but the bugreport * is never given back directly to the app that requested it. * available to the user. The user may choose to share it with another app, but the bugreport is * never given back directly to the app that requested it. * * @param params {@link BugreportParams} that specify what kind of a bugreport should * be taken, please note that not all kinds of bugreport allow for a * progress notification * @param params {@link BugreportParams} that specify what kind of a bugreport should be taken, * please note that not all kinds of bugreport allow for a progress notification * @param shareTitle title on the final share notification * @param shareDescription description on the final share notification * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull BugreportParams params, @Nullable CharSequence shareTitle, public void requestBugreport( @NonNull BugreportParams params, @Nullable CharSequence shareTitle, @Nullable CharSequence shareDescription) { try { String title = shareTitle == null ? null : shareTitle.toString(); String description = shareDescription == null ? null : shareDescription.toString(); ActivityManager.getService().requestBugReportWithDescription(title, description, params.getMode()); ActivityManager.getService() .requestBugReportWithDescription(title, description, params.getMode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading @@ -236,8 +289,8 @@ public final class BugreportManager { private final BugreportCallback mCallback; private final boolean mIsScreenshotRequested; DumpstateListener(Executor executor, BugreportCallback callback, boolean isScreenshotRequested) { DumpstateListener( Executor executor, BugreportCallback callback, boolean isScreenshotRequested) { mExecutor = executor; mCallback = callback; mIsScreenshotRequested = isScreenshotRequested; Loading @@ -247,9 +300,7 @@ public final class BugreportManager { public void onProgress(int progress) throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onProgress(progress); }); mExecutor.execute(() -> mCallback.onProgress(progress)); } finally { Binder.restoreCallingIdentity(identity); } Loading @@ -259,9 +310,7 @@ public final class BugreportManager { public void onError(int errorCode) throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onError(errorCode); }); mExecutor.execute(() -> mCallback.onError(errorCode)); } finally { Binder.restoreCallingIdentity(identity); } Loading @@ -271,9 +320,7 @@ public final class BugreportManager { public void onFinished() throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onFinished(); }); mExecutor.execute(() -> mCallback.onFinished()); } finally { Binder.restoreCallingIdentity(identity); } Loading @@ -288,20 +335,19 @@ public final class BugreportManager { Handler mainThreadHandler = new Handler(Looper.getMainLooper()); mainThreadHandler.post( () -> { int message = success ? R.string.bugreport_screenshot_success_toast int message = success ? R.string.bugreport_screenshot_success_toast : R.string.bugreport_screenshot_failure_toast; Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); }); } @Override public void onUiIntensiveBugreportDumpsFinished() throws RemoteException { public void onUiIntensiveBugreportDumpsFinished() throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onEarlyReportFinished(); }); mExecutor.execute(() -> mCallback.onEarlyReportFinished()); } finally { Binder.restoreCallingIdentity(identity); } Loading
services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +39 −13 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Binder; import android.os.BugreportParams; Loading @@ -31,6 +32,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserManager; import android.telephony.TelephonyManager; import android.util.ArraySet; import android.util.Slog; Loading @@ -53,11 +55,13 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private final Object mLock = new Object(); private final Context mContext; private final AppOpsManager mAppOps; private final TelephonyManager mTelephonyManager; private final ArraySet<String> mBugreportWhitelistedPackages; BugreportManagerServiceImpl(Context context) { mContext = context; mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mAppOps = context.getSystemService(AppOpsManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mBugreportWhitelistedPackages = SystemConfig.getInstance().getBugreportWhitelistedPackages(); } Loading @@ -67,11 +71,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { public void startBugreport(int callingUidUnused, String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport"); Objects.requireNonNull(callingPackage); Objects.requireNonNull(bugreportFd); Objects.requireNonNull(listener); validateBugreportMode(bugreportMode); int callingUid = Binder.getCallingUid(); enforcePermission(callingPackage, callingUid, bugreportMode == BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */); final long identity = Binder.clearCallingIdentity(); try { ensureIsPrimaryUser(); Loading @@ -79,13 +86,6 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { Binder.restoreCallingIdentity(identity); } int callingUid = Binder.getCallingUid(); mAppOps.checkPackage(callingUid, callingPackage); if (!mBugreportWhitelistedPackages.contains(callingPackage)) { throw new SecurityException( callingPackage + " is not whitelisted to use Bugreport API"); } synchronized (mLock) { startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode, listener, isScreenshotRequested); Loading @@ -93,12 +93,10 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } @Override @RequiresPermission(android.Manifest.permission.DUMP) @RequiresPermission(android.Manifest.permission.DUMP) // or carrier privileges public void cancelBugreport(int callingUidUnused, String callingPackage) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "cancelBugreport"); int callingUid = Binder.getCallingUid(); mAppOps.checkPackage(callingUid, callingPackage); enforcePermission(callingPackage, callingUid, true /* checkCarrierPrivileges */); synchronized (mLock) { IDumpstate ds = getDumpstateBinderServiceLocked(); Loading Loading @@ -134,6 +132,34 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } } private void enforcePermission( String callingPackage, int callingUid, boolean checkCarrierPrivileges) { mAppOps.checkPackage(callingUid, callingPackage); // To gain access through the DUMP permission, the OEM has to allow this package explicitly // via sysconfig and privileged permissions. if (mBugreportWhitelistedPackages.contains(callingPackage) && mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) == PackageManager.PERMISSION_GRANTED) { return; } // For carrier privileges, this can include user-installed apps. This is essentially a // function of the current active SIM(s) in the device to let carrier apps through. if (checkCarrierPrivileges && mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { return; } String message = callingPackage + " does not hold the DUMP permission or is not bugreport-whitelisted " + (checkCarrierPrivileges ? "and does not have carrier privileges " : "") + "to request a bugreport"; Slog.w(TAG, message); throw new SecurityException(message); } /** * Validates that the current user is the primary user. * Loading