Loading core/java/android/os/BugreportManager.java +62 −37 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.os; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -27,6 +28,7 @@ import android.os.IBinder.DeathRecipient; import java.io.FileDescriptor; 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. Loading @@ -47,47 +49,46 @@ public class BugreportManager { } /** * An interface describing the listener for bugreport progress and status. * An interface describing the callback for bugreport progress and status. */ public interface BugreportListener { /** * Called when there is a progress update. * @param progress the progress in [0.0, 100.0] */ void onProgress(float progress); public abstract static class BugreportCallback { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { BUGREPORT_ERROR_INVALID_INPUT, BUGREPORT_ERROR_RUNTIME BUGREPORT_ERROR_RUNTIME, BUGREPORT_ERROR_USER_DENIED_CONSENT, BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT }) /** Possible error codes taking a bugreport can encounter */ @interface BugreportErrorCode {} public @interface BugreportErrorCode {} /** The input options were invalid */ int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; public static final int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; /** A runtime error occured */ int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; public static final int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; /** User denied consent to share the bugreport */ int BUGREPORT_ERROR_USER_DENIED_CONSENT = public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; /** The request to get user consent timed out. */ int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT; /** * Called when there is a progress update. * @param progress the progress in [0.0, 100.0] */ public void onProgress(float progress) {} /** * Called when taking bugreport resulted in an error. * * @param errorCode the error that occurred. Possible values are * {@code BUGREPORT_ERROR_INVALID_INPUT}, * {@code BUGREPORT_ERROR_RUNTIME}, * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}, * {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT}. * * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not * consent to sharing the bugreport with the calling app. * Loading @@ -95,19 +96,19 @@ public class BugreportManager { * out, but the bugreport could be available in the internal directory of dumpstate for * manual retrieval. */ void onError(@BugreportErrorCode int errorCode); public void onError(@BugreportErrorCode int errorCode) {} /** * Called when taking bugreport finishes successfully. */ void onFinished(); public void onFinished() {} } /** * Starts a bugreport. * * <p>This starts a bugreport in the background. However the call itself can take several * seconds to return in the worst case. {@code listener} will receive progress and status * 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 Loading @@ -118,19 +119,23 @@ public class BugreportManager { * @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 listener callback for progress and status updates * @param callback callback for progress and status updates */ @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull FileDescriptor bugreportFd, @Nullable FileDescriptor screenshotFd, @NonNull BugreportParams params, @NonNull BugreportListener listener) { public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd, @Nullable ParcelFileDescriptor screenshotFd, @NonNull BugreportParams params, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback) { // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. DumpstateListener dsListener = new DumpstateListener(listener); DumpstateListener dsListener = new DumpstateListener(executor, callback); try { // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, mContext.getOpPackageName(), bugreportFd, screenshotFd, mContext.getOpPackageName(), (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()), (screenshotFd != null ? screenshotFd.getFileDescriptor() : new FileDescriptor()), params.getMode(), dsListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -151,10 +156,12 @@ public class BugreportManager { private final class DumpstateListener extends IDumpstateListener.Stub implements DeathRecipient { private final BugreportListener mListener; private final Executor mExecutor; private final BugreportCallback mCallback; DumpstateListener(@Nullable BugreportListener listener) { mListener = listener; DumpstateListener(Executor executor, @Nullable BugreportCallback callback) { mExecutor = executor; mCallback = callback; } @Override Loading @@ -164,19 +171,37 @@ public class BugreportManager { @Override public void onProgress(int progress) throws RemoteException { mListener.onProgress(progress); final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onProgress(progress); }); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onError(int errorCode) throws RemoteException { mListener.onError(errorCode); final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onError(errorCode); }); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onFinished() throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mListener.onFinished(); mExecutor.execute(() -> { mCallback.onFinished(); }); } finally { Binder.restoreCallingIdentity(identity); // The bugreport has finished. Let's shutdown the service to minimize its footprint. cancelBugreport(); } Loading Loading
core/java/android/os/BugreportManager.java +62 −37 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.os; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; Loading @@ -27,6 +28,7 @@ import android.os.IBinder.DeathRecipient; import java.io.FileDescriptor; 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. Loading @@ -47,47 +49,46 @@ public class BugreportManager { } /** * An interface describing the listener for bugreport progress and status. * An interface describing the callback for bugreport progress and status. */ public interface BugreportListener { /** * Called when there is a progress update. * @param progress the progress in [0.0, 100.0] */ void onProgress(float progress); public abstract static class BugreportCallback { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = { BUGREPORT_ERROR_INVALID_INPUT, BUGREPORT_ERROR_RUNTIME BUGREPORT_ERROR_RUNTIME, BUGREPORT_ERROR_USER_DENIED_CONSENT, BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT }) /** Possible error codes taking a bugreport can encounter */ @interface BugreportErrorCode {} public @interface BugreportErrorCode {} /** The input options were invalid */ int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; public static final int BUGREPORT_ERROR_INVALID_INPUT = IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT; /** A runtime error occured */ int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; public static final int BUGREPORT_ERROR_RUNTIME = IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR; /** User denied consent to share the bugreport */ int BUGREPORT_ERROR_USER_DENIED_CONSENT = public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT; /** The request to get user consent timed out. */ int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT; /** * Called when there is a progress update. * @param progress the progress in [0.0, 100.0] */ public void onProgress(float progress) {} /** * Called when taking bugreport resulted in an error. * * @param errorCode the error that occurred. Possible values are * {@code BUGREPORT_ERROR_INVALID_INPUT}, * {@code BUGREPORT_ERROR_RUNTIME}, * {@code BUGREPORT_ERROR_USER_DENIED_CONSENT}, * {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT}. * * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not * consent to sharing the bugreport with the calling app. * Loading @@ -95,19 +96,19 @@ public class BugreportManager { * out, but the bugreport could be available in the internal directory of dumpstate for * manual retrieval. */ void onError(@BugreportErrorCode int errorCode); public void onError(@BugreportErrorCode int errorCode) {} /** * Called when taking bugreport finishes successfully. */ void onFinished(); public void onFinished() {} } /** * Starts a bugreport. * * <p>This starts a bugreport in the background. However the call itself can take several * seconds to return in the worst case. {@code listener} will receive progress and status * 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 Loading @@ -118,19 +119,23 @@ public class BugreportManager { * @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 listener callback for progress and status updates * @param callback callback for progress and status updates */ @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull FileDescriptor bugreportFd, @Nullable FileDescriptor screenshotFd, @NonNull BugreportParams params, @NonNull BugreportListener listener) { public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd, @Nullable ParcelFileDescriptor screenshotFd, @NonNull BugreportParams params, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback) { // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary. DumpstateListener dsListener = new DumpstateListener(listener); DumpstateListener dsListener = new DumpstateListener(executor, callback); try { // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport(-1 /* callingUid */, mContext.getOpPackageName(), bugreportFd, screenshotFd, mContext.getOpPackageName(), (bugreportFd != null ? bugreportFd.getFileDescriptor() : new FileDescriptor()), (screenshotFd != null ? screenshotFd.getFileDescriptor() : new FileDescriptor()), params.getMode(), dsListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); Loading @@ -151,10 +156,12 @@ public class BugreportManager { private final class DumpstateListener extends IDumpstateListener.Stub implements DeathRecipient { private final BugreportListener mListener; private final Executor mExecutor; private final BugreportCallback mCallback; DumpstateListener(@Nullable BugreportListener listener) { mListener = listener; DumpstateListener(Executor executor, @Nullable BugreportCallback callback) { mExecutor = executor; mCallback = callback; } @Override Loading @@ -164,19 +171,37 @@ public class BugreportManager { @Override public void onProgress(int progress) throws RemoteException { mListener.onProgress(progress); final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onProgress(progress); }); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onError(int errorCode) throws RemoteException { mListener.onError(errorCode); final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { mCallback.onError(errorCode); }); } finally { Binder.restoreCallingIdentity(identity); } } @Override public void onFinished() throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { mListener.onFinished(); mExecutor.execute(() -> { mCallback.onFinished(); }); } finally { Binder.restoreCallingIdentity(identity); // The bugreport has finished. Let's shutdown the service to minimize its footprint. cancelBugreport(); } Loading