diff --git a/core/api/current.txt b/core/api/current.txt index 629ff726cd197..954f0942cc79f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32138,9 +32138,11 @@ package android.os { method public void onEarlyReportFinished(); method public void onError(int); method public void onFinished(); + method public void onFinished(@NonNull String); 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_NO_BUGREPORT_TO_RETRIEVE = 6; // 0x6 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 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index adf7ff0ae1980..fe73faaf1c50a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9934,6 +9934,7 @@ package android.os { public final class BugreportManager { method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void preDumpUiData(); method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence); + method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread public void retrieveBugreport(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); method @RequiresPermission(android.Manifest.permission.DUMP) @WorkerThread 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); } @@ -9942,6 +9943,7 @@ package android.os { ctor public BugreportParams(int, int); method public int getFlags(); method public int getMode(); + field public static final int BUGREPORT_FLAG_DEFER_CONSENT = 2; // 0x2 field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1 field public static final int BUGREPORT_MODE_FULL = 0; // 0x0 field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1 diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 222e88f9d3e18..0a7eb438993ef 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -16,6 +16,7 @@ package android.os; +import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FloatRange; import android.annotation.IntDef; @@ -69,7 +70,10 @@ public final class BugreportManager { * An interface describing the callback for bugreport progress and status. * *
Callers will receive {@link #onProgress} calls as the bugreport progresses, followed by a - * terminal call to either {@link #onFinished} or {@link #onError}. + * terminal call to either {@link #onFinished} or {@link #onError}. Note that + * {@link #onFinished(String)} will only be invoked when calling {@code startBugreport} with the + * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag set. Otherwise, + * {@link #onFinished()} will be invoked. * *
If an issue is encountered while starting the bugreport asynchronously, callers will * receive an {@link #onError} call without any {@link #onProgress} callbacks. @@ -88,7 +92,8 @@ public final class BugreportManager { BUGREPORT_ERROR_RUNTIME, BUGREPORT_ERROR_USER_DENIED_CONSENT, BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT, - BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS + BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS, + BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE }) public @interface BugreportErrorCode {} @@ -115,6 +120,10 @@ public final class BugreportManager { public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS; + /** There is no bugreport to retrieve for the caller. */ + public static final int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE = + IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE; + /** * Called when there is a progress update. * @@ -137,9 +146,25 @@ public final class BugreportManager { */ public void onError(@BugreportErrorCode int errorCode) {} - /** Called when taking bugreport finishes successfully. */ + /** Called when taking bugreport finishes successfully. + * + *
This callback will be invoked if the + * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set. Otherwise, the + * {@link #onFinished(String)} callback will be invoked. + */ public void onFinished() {} + /** Called when taking bugreport finishes successfully. + * + *
This callback will only be invoked if the + * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is set. Otherwise, the + * {@link #onFinished()} callback will be invoked. + * + * @param bugreportFile the absolute path of the generated bugreport file. + + */ + public void onFinished(@NonNull String bugreportFile) {} + /** * Called when it is ready for calling app to show UI, showing any extra UI before this * callback can interfere with bugreport generation. @@ -178,7 +203,9 @@ public final class BugreportManager { * updates. * *
The bugreport artifacts will be copied over to the given file descriptors only if the user - * consents to sharing with the calling app. + * consents to sharing with the calling app. If + * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} is set, user consent will be deferred + * and no files will be copied to the given file descriptors. * *
{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}. * @@ -205,7 +232,9 @@ public final class BugreportManager { Preconditions.checkNotNull(executor); Preconditions.checkNotNull(callback); - boolean isScreenshotRequested = screenshotFd != null; + boolean deferConsent = + (params.getFlags() & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0; + boolean isScreenshotRequested = screenshotFd != null || deferConsent; if (screenshotFd == null) { // Binder needs a valid File Descriptor to be passed screenshotFd = @@ -213,7 +242,7 @@ public final class BugreportManager { new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY); } DumpstateListener dsListener = - new DumpstateListener(executor, callback, isScreenshotRequested); + new DumpstateListener(executor, callback, isScreenshotRequested, deferConsent); // Note: mBinder can get callingUid from the binder transaction. mBinder.startBugreport( -1 /* callingUid */, @@ -237,6 +266,58 @@ public final class BugreportManager { } } + /** + * Retrieves a previously generated bugreport. + * + *
The previously generated bugreport must have been generated by calling {@link + * #startBugreport(ParcelFileDescriptor, ParcelFileDescriptor, BugreportParams, + * Executor, BugreportCallback)} with the {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} + * flag set. The bugreport file returned by the {@link BugreportCallback#onFinished(String)} + * callback for a previously generated bugreport must be passed to this method. A caller may + * only retrieve bugreports that they have previously requested. + * + *
The bugreport artifacts will be copied over to the given file descriptor only if the user + * consents to sharing with the calling app. + * + *
{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}. + * + *
The caller may only request to retrieve a given bugreport once. Subsequent calls will fail + * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}. + * + * @param bugreportFile the identifier for a bugreport that was previously generated for this + * caller using {@code startBugreport}. + * @param bugreportFd file to copy over the previous bugreport. This should be opened in + * write-only, append mode. + * @param executor the executor to execute callback methods. + * @param callback callback for progress and status updates. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.DUMP) + @WorkerThread + public void retrieveBugreport( + @NonNull String bugreportFile, + @NonNull ParcelFileDescriptor bugreportFd, + @NonNull @CallbackExecutor Executor executor, + @NonNull BugreportCallback callback + ) { + try { + Preconditions.checkNotNull(bugreportFile); + Preconditions.checkNotNull(bugreportFd); + Preconditions.checkNotNull(executor); + Preconditions.checkNotNull(callback); + DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false); + mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(), + bugreportFd.getFileDescriptor(), + bugreportFile, + dsListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + IoUtils.closeQuietly(bugreportFd); + } + } + /** * Starts a connectivity bugreport. * @@ -316,7 +397,7 @@ public final class BugreportManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.DUMP) + @RequiresPermission(Manifest.permission.DUMP) public void requestBugreport( @NonNull BugreportParams params, @Nullable CharSequence shareTitle, @@ -335,12 +416,15 @@ public final class BugreportManager { private final Executor mExecutor; private final BugreportCallback mCallback; private final boolean mIsScreenshotRequested; + private final boolean mIsConsentDeferred; DumpstateListener( - Executor executor, BugreportCallback callback, boolean isScreenshotRequested) { + Executor executor, BugreportCallback callback, boolean isScreenshotRequested, + boolean isConsentDeferred) { mExecutor = executor; mCallback = callback; mIsScreenshotRequested = isScreenshotRequested; + mIsConsentDeferred = isConsentDeferred; } @Override @@ -364,10 +448,14 @@ public final class BugreportManager { } @Override - public void onFinished() throws RemoteException { + public void onFinished(String bugreportFile) throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> mCallback.onFinished()); + if (mIsConsentDeferred) { + mExecutor.execute(() -> mCallback.onFinished(bugreportFile)); + } else { + mExecutor.execute(() -> mCallback.onFinished()); + } } finally { Binder.restoreCallingIdentity(identity); } diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index 990883f6d83c3..d9d14b0382373 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -21,6 +21,7 @@ import android.annotation.SystemApi; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * Parameters that specify what kind of bugreport should be taken. @@ -125,7 +126,8 @@ public final class BugreportParams { */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = { - BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA + BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA, + BUGREPORT_FLAG_DEFER_CONSENT }) public @interface BugreportFlag {} @@ -135,4 +137,14 @@ public final class BugreportParams { */ public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = IDumpstate.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA; + + /** + * Flag for deferring user consent. + * + *
This flag should be used in cases where it may not be possible for the user to respond
+ * to a consent dialog immediately, such as when the user is driving. The generated bugreport
+ * may be retrieved at a later time using {@link BugreportManager#retrieveBugreport(
+ * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
+ */
+ public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT;
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index cb37c079d2d09..a719d77604b10 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -677,7 +677,7 @@ public class BugreportReceiverTest {
if (mScreenshotFd != null) {
writeScreenshotFile(mScreenshotFd, SCREENSHOT_CONTENT);
}
- mIDumpstateListener.onFinished();
+ mIDumpstateListener.onFinished("");
getInstrumentation().waitForIdleSync();
}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 2fdc4cd5f7c1b..e698c4b5b3f1e 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.os;
+import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
@@ -25,6 +26,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
+import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
import android.os.IDumpstate;
import android.os.IDumpstateListener;
@@ -35,10 +37,13 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemConfig;
import java.io.FileDescriptor;
@@ -60,18 +65,105 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private final AppOpsManager mAppOps;
private final TelephonyManager mTelephonyManager;
private final ArraySet