summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Gavin Corkery <gavincorkery@google.com> 2022-11-23 17:59:58 +0000
committer Gavin Corkery <gavincorkery@google.com> 2023-01-30 23:17:26 +0000
commitdc452e0884d039cc11d557746cb1360d782ac151 (patch)
tree14e97d6249e6f678e075579bfa0e9e77c5b83936
parenta2acce8608991b3db6626ea0baac8f84193c8d70 (diff)
Add retrieveBugreport API
Adds API support for deferring consent while generating a bugreport, along with support for the same caller to retrieve a bugreport that was previously generated by them. Test: atest CtsRootBugreportTestCases Test: atest BugreportManagerServiceImplTest Bug: 245328405 Change-Id: I6ea42fdc3609344aad3126c7af0a6f32666e7438
-rw-r--r--core/api/current.txt2
-rw-r--r--core/api/system-current.txt2
-rw-r--r--core/java/android/os/BugreportManager.java108
-rw-r--r--core/java/android/os/BugreportParams.java14
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java2
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java166
-rw-r--r--services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java152
-rw-r--r--services/tests/servicestests/src/com/android/server/os/OWNERS2
-rw-r--r--services/tests/servicestests/src/com/android/server/os/TEST_MAPPING12
9 files changed, 439 insertions, 21 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 978713abe84d..7c73cafcd865 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31900,9 +31900,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 8733861138cc..b96b02767faf 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9933,6 +9933,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);
}
@@ -9941,6 +9942,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 222e88f9d3e1..0a7eb438993e 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.
*
* <p>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.
*
* <p>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.
+ *
+ * <p>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.
+ *
+ * <p>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.
*
* <p>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.
*
* <p>{@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 */,
@@ -238,6 +267,58 @@ public final class BugreportManager {
}
/**
+ * Retrieves a previously generated bugreport.
+ *
+ * <p>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.
+ *
+ * <p>The bugreport artifacts will be copied over to the given file descriptor only if the user
+ * consents to sharing with the calling app.
+ *
+ * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
+ *
+ * <p>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.
*
* <p>The connectivity bugreport is a specialized version of bugreport that only includes
@@ -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 990883f6d83c..d9d14b038237 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.
+ *
+ * <p>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 cb37c079d2d0..a719d77604b1 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 2fdc4cd5f7c1..e698c4b5b3f1 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<String> mBugreportWhitelistedPackages;
+ private final BugreportFileManager mBugreportFileManager;
@GuardedBy("mLock")
private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
+ /** Helper class for associating previously generated bugreports with their callers. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static class BugreportFileManager {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles;
+
+ BugreportFileManager() {
+ mBugreportFiles = new ArrayMap<>();
+ }
+
+ /**
+ * Checks that a given file was generated on behalf of the given caller. If the file was
+ * generated on behalf of the caller, it is removed from the bugreport mapping so that it
+ * may not be retrieved again. If the file was not generated on behalf of the caller, an
+ * {@link IllegalArgumentException} is thrown.
+ *
+ * @param callingInfo a (uid, package name) pair identifying the caller
+ * @param bugreportFile the file name which was previously given to the caller in the
+ * {@link BugreportCallback#onFinished(String)} callback.
+ *
+ * @throws IllegalArgumentException if {@code bugreportFile} is not associated with
+ * {@code callingInfo}.
+ */
+ void ensureCallerPreviouslyGeneratedFile(
+ Pair<Integer, String> callingInfo, String bugreportFile) {
+ synchronized (mLock) {
+ ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
+ if (bugreportFilesForCaller != null
+ && bugreportFilesForCaller.contains(bugreportFile)) {
+ bugreportFilesForCaller.remove(bugreportFile);
+ if (bugreportFilesForCaller.isEmpty()) {
+ mBugreportFiles.remove(callingInfo);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "File " + bugreportFile + " was not generated"
+ + " on behalf of calling package " + callingInfo.second);
+ }
+ }
+ }
+
+ /**
+ * Associates a bugreport file with a caller, which is identified as a
+ * (uid, package name) pair.
+ */
+ void addBugreportFileForCaller(Pair<Integer, String> caller, String bugreportFile) {
+ synchronized (mLock) {
+ if (!mBugreportFiles.containsKey(caller)) {
+ mBugreportFiles.put(caller, new ArraySet<>());
+ }
+ ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller);
+ bugreportFilesForCaller.add(bugreportFile);
+ }
+ }
+ }
+
+ static class Injector {
+ Context mContext;
+ ArraySet<String> mAllowlistedPackages;
+
+ Injector(Context context, ArraySet<String> allowlistedPackages) {
+ mContext = context;
+ mAllowlistedPackages = allowlistedPackages;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ ArraySet<String> getAllowlistedPackages() {
+ return mAllowlistedPackages;
+ }
+
+ }
+
BugreportManagerServiceImpl(Context context) {
- mContext = context;
- mAppOps = context.getSystemService(AppOpsManager.class);
- mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ this(new Injector(context, SystemConfig.getInstance().getBugreportWhitelistedPackages()));
+
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ BugreportManagerServiceImpl(Injector injector) {
+ mContext = injector.getContext();
+ mAppOps = mContext.getSystemService(AppOpsManager.class);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ mBugreportFileManager = new BugreportFileManager();
mBugreportWhitelistedPackages =
- SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ injector.getAllowlistedPackages();
}
+
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
public void preDumpUiData(String callingPackage) {
@@ -135,6 +227,50 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
}
+ @Override
+ @RequiresPermission(Manifest.permission.DUMP)
+ public void retrieveBugreport(int callingUidUnused, String callingPackage,
+ FileDescriptor bugreportFd, String bugreportFile, IDumpstateListener listener) {
+ int callingUid = Binder.getCallingUid();
+ enforcePermission(callingPackage, callingUid, false);
+
+ try {
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ new Pair<>(callingUid, callingPackage), bugreportFile);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, e.getMessage());
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (isDumpstateBinderServiceRunningLocked()) {
+ Slog.w(TAG, "'dumpstate' is already running. Cannot retrieve a bugreport"
+ + " while another one is currently in progress.");
+ reportError(listener,
+ IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
+ return;
+ }
+
+ IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
+ if (ds == null) {
+ Slog.w(TAG, "Unable to get bugreport service");
+ reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
+ return;
+ }
+
+ // Wrap the listener so we can intercept binder events directly.
+ IDumpstateListener myListener = new DumpstateListener(listener, ds,
+ new Pair<>(callingUid, callingPackage));
+ try {
+ ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
+ bugreportFile, myListener);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException in retrieveBugreport", e);
+ }
+ }
+ }
+
private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
if (mode != BugreportParams.BUGREPORT_MODE_FULL
&& mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -148,7 +284,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
private void validateBugreportFlags(int flags) {
- flags = clearBugreportFlag(flags, BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA);
+ flags = clearBugreportFlag(flags,
+ BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
+ | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT);
if (flags != 0) {
Slog.w(TAG, "Unknown bugreport flags: " + flags);
throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
@@ -298,6 +436,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
}
+ boolean isConsentDeferred =
+ (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
+
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
if (ds == null) {
Slog.w(TAG, "Unable to get bugreport service");
@@ -306,7 +447,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
// Wrap the listener so we can intercept binder events directly.
- IDumpstateListener myListener = new DumpstateListener(listener, ds);
+ IDumpstateListener myListener = new DumpstateListener(listener, ds,
+ isConsentDeferred ? new Pair<>(callingUid, callingPackage) : null);
try {
ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
bugreportFlags, myListener, isScreenshotRequested);
@@ -405,10 +547,13 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private final IDumpstateListener mListener;
private final IDumpstate mDs;
private boolean mDone = false;
+ private final Pair<Integer, String> mCaller;
- DumpstateListener(IDumpstateListener listener, IDumpstate ds) {
+ DumpstateListener(IDumpstateListener listener, IDumpstate ds,
+ @Nullable Pair<Integer, String> caller) {
mListener = listener;
mDs = ds;
+ mCaller = caller;
try {
mDs.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -430,11 +575,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
@Override
- public void onFinished() throws RemoteException {
+ public void onFinished(String bugreportFile) throws RemoteException {
synchronized (mLock) {
mDone = true;
}
- mListener.onFinished();
+ if (mCaller != null) {
+ mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+ }
+ mListener.onFinished(bugreportFile);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
new file mode 100644
index 000000000000..52c6777b70af
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.BugreportManager.BugreportCallback;
+import android.os.IBinder;
+import android.os.IDumpstateListener;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class BugreportManagerServiceImplTest {
+
+ Context mContext;
+ BugreportManagerServiceImpl mService;
+ BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+
+ int mCallingUid = 1234;
+ String mCallingPackage = "test.package";
+
+ String mBugreportFile = "bugreport-file.zip";
+ String mBugreportFile2 = "bugreport-file2.zip";
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ ArraySet<String> mAllowlistedPackages = new ArraySet<>();
+ mAllowlistedPackages.add(mContext.getPackageName());
+ mService = new BugreportManagerServiceImpl(
+ new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages));
+ mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager();
+ }
+
+ @Test
+ public void testBugreportFileManagerFileExists() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile);
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ callingInfo, "unknown-file.zip"));
+
+ // No exception should be thrown.
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+ }
+
+ @Test
+ public void testBugreportFileManagerMultipleFiles() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile);
+ mBugreportFileManager.addBugreportFileForCaller(
+ callingInfo, mBugreportFile2);
+
+ // No exception should be thrown.
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile2);
+ }
+
+ @Test
+ public void testBugreportFileManagerFileDoesNotExist() {
+ Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ callingInfo, "test-file.zip"));
+ }
+
+ @Test
+ public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ Listener listener = new Listener(latch);
+ mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(),
+ new FileDescriptor(), mBugreportFile, listener);
+ assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(listener.getErrorCode()).isEqualTo(
+ BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
+ }
+
+ private static class Listener implements IDumpstateListener {
+ CountDownLatch mLatch;
+ int mErrorCode;
+
+ Listener(CountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public void onProgress(int progress) throws RemoteException {
+ }
+
+ @Override
+ public void onError(int errorCode) throws RemoteException {
+ mErrorCode = errorCode;
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onFinished(String bugreportFile) throws RemoteException {
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onScreenshotTaken(boolean success) throws RemoteException {
+ }
+
+ @Override
+ public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
+ }
+
+ int getErrorCode() {
+ return mErrorCode;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/os/OWNERS b/services/tests/servicestests/src/com/android/server/os/OWNERS
new file mode 100644
index 000000000000..feb8011715b3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/OWNERS
@@ -0,0 +1,2 @@
+# Component 153446
+include /platform/frameworks/native:/cmds/dumpstate/OWNERS \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
new file mode 100644
index 000000000000..9902446d7324
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "BugreportManagerServiceImplTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.os."
+ }
+ ]
+ }
+ ]
+}