summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kholoud Mohamed <kholoudm@google.com> 2023-10-27 09:36:07 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-10-27 09:36:07 +0000
commite594a9255205d1f1ca8e27d2a22f25ff61c83e2f (patch)
treeebe0f21cbf05a3436d3b1f18f77c7b2dae679289
parent5a84903b7b9ebffd76b2aa3be9a56da881984eba (diff)
parent05005e9a9481c2361e1dadb9ee1d01d718439e6e (diff)
Merge "Add new bugreport retrieval capabilities" into main
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/os/BugreportManager.java8
-rw-r--r--core/java/android/os/BugreportParams.java22
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java113
-rw-r--r--services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java24
6 files changed, 136 insertions, 34 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e130206d49fa..83234a5dbce2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2148,7 +2148,9 @@ package android.os {
}
public final class BugreportParams {
+ field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 4; // 0x4
field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+ field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7
}
public class Build {
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 58def6ef7961..960e84d671e7 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -26,6 +26,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
import android.annotation.WorkerThread;
import android.app.ActivityManager;
import android.content.Context;
@@ -280,8 +281,8 @@ public final class BugreportManager {
*
* <p>{@link BugreportManager} takes ownership of {@code bugreportFd}.
*
- * <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}.
+ * <p>The caller can reattempt to retrieve the bugreport multiple times if the user has not
+ * consented on previous attempts.
*
* @param bugreportFile the identifier for a bugreport that was previously generated for this
* caller using {@code startBugreport}.
@@ -294,6 +295,7 @@ public final class BugreportManager {
@SystemApi
@RequiresPermission(Manifest.permission.DUMP)
@WorkerThread
+ @UserHandleAware
public void retrieveBugreport(
@NonNull String bugreportFile,
@NonNull ParcelFileDescriptor bugreportFd,
@@ -307,8 +309,10 @@ public final class BugreportManager {
Preconditions.checkNotNull(callback);
DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false);
mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(),
+ mContext.getUserId(),
bugreportFd.getFileDescriptor(),
bugreportFile,
+ /* keepBugreportOnRetrieval = */ false,
dsListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index e8ad303c4208..8510084c309d 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -20,6 +20,7 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -128,6 +129,8 @@ public final class BugreportParams {
*
* @hide
*/
+ @TestApi
+ @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING;
/**
@@ -145,7 +148,8 @@ public final class BugreportParams {
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
- BUGREPORT_FLAG_DEFER_CONSENT
+ BUGREPORT_FLAG_DEFER_CONSENT,
+ BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL
})
public @interface BugreportFlag {}
@@ -165,4 +169,20 @@ public final class BugreportParams {
* String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
*/
public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT;
+
+ /**
+ * Flag for keeping a bugreport stored even after it has been retrieved via
+ * {@link BugreportManager#retrieveBugreport}.
+ *
+ * <p>This flag can only be used when {@link #BUGREPORT_FLAG_DEFER_CONSENT} is set.
+ * The bugreport may be retrieved multiple times using
+ * {@link BugreportManager#retrieveBugreport(
+ * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
+ public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL =
+ IDumpstate.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL;
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 961e9d3dcfff..3985a0e7f46c 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -155,6 +155,7 @@ java_library_static {
"service-permission.stubs.system_server",
"service-rkp.stubs.system_server",
"service-sdksandbox.stubs.system_server",
+ "device_policy_aconfig_flags_lib",
],
plugins: ["ImmutabilityAnnotationProcessor"],
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1134714bce55..e57ea0f65804 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -16,6 +16,8 @@
package com.android.server.os;
+import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
+
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -52,8 +54,10 @@ import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.HashSet;
import java.util.Objects;
import java.util.OptionalInt;
+import java.util.Set;
/**
* Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -101,10 +105,12 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
new ArrayMap<>();
+ @GuardedBy("mLock")
+ private final Set<String> mBugreportFilesToPersist = new HashSet<>();
+
/**
* 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
+ * not generated on behalf of the caller, an
* {@link IllegalArgumentException} is thrown.
*
* @param callingInfo a (uid, package name) pair identifying the caller
@@ -114,35 +120,76 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
* @throws IllegalArgumentException if {@code bugreportFile} is not associated with
* {@code callingInfo}.
*/
+ @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
+ conditional = true)
void ensureCallerPreviouslyGeneratedFile(
- Pair<Integer, String> callingInfo, String bugreportFile) {
+ Context context, Pair<Integer, String> callingInfo, int userId,
+ 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);
+ if (onboardingBugreportV2Enabled()) {
+ final int uidForUser = Binder.withCleanCallingIdentity(() -> {
+ try {
+ return context.getPackageManager()
+ .getPackageUidAsUser(callingInfo.second, userId);
+ } catch (PackageManager.NameNotFoundException exception) {
+ throwInvalidBugreportFileForCallerException(
+ bugreportFile, callingInfo.second);
+ return -1;
+ }
+ });
+ if (uidForUser != callingInfo.first && context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ callingInfo.second + " does not hold the "
+ + "INTERACT_ACROSS_USERS permission to access "
+ + "cross-user bugreports.");
+ }
+ ArraySet<String> bugreportFilesForUid = mBugreportFiles.get(
+ new Pair<>(uidForUser, callingInfo.second));
+ if (bugreportFilesForUid == null
+ || !bugreportFilesForUid.contains(bugreportFile)) {
+ throwInvalidBugreportFileForCallerException(
+ bugreportFile, callingInfo.second);
}
} else {
- throw new IllegalArgumentException(
- "File " + bugreportFile + " was not generated"
- + " on behalf of calling package " + callingInfo.second);
+ ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
+ if (bugreportFilesForCaller != null
+ && bugreportFilesForCaller.contains(bugreportFile)) {
+ bugreportFilesForCaller.remove(bugreportFile);
+ if (bugreportFilesForCaller.isEmpty()) {
+ mBugreportFiles.remove(callingInfo);
+ }
+ } else {
+ throwInvalidBugreportFileForCallerException(
+ bugreportFile, callingInfo.second);
+
+ }
}
}
}
+ private static void throwInvalidBugreportFileForCallerException(
+ String bugreportFile, String packageName) {
+ throw new IllegalArgumentException("File " + bugreportFile + " was not generated on"
+ + " behalf of calling package " + packageName);
+ }
+
/**
* Associates a bugreport file with a caller, which is identified as a
* (uid, package name) pair.
*/
- void addBugreportFileForCaller(Pair<Integer, String> caller, String bugreportFile) {
+ void addBugreportFileForCaller(
+ Pair<Integer, String> caller, String bugreportFile, boolean keepOnRetrieval) {
synchronized (mLock) {
if (!mBugreportFiles.containsKey(caller)) {
mBugreportFiles.put(caller, new ArraySet<>());
}
ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller);
bugreportFilesForCaller.add(bugreportFile);
+ if ((onboardingBugreportV2Enabled()) && keepOnRetrieval) {
+ mBugreportFilesToPersist.add(bugreportFile);
+ }
}
}
}
@@ -246,16 +293,17 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
@Override
- @RequiresPermission(Manifest.permission.DUMP)
- public void retrieveBugreport(int callingUidUnused, String callingPackage,
- FileDescriptor bugreportFd, String bugreportFile, IDumpstateListener listener) {
+ @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
+ public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
+ FileDescriptor bugreportFd, String bugreportFile,
+ boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, false);
Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
try {
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- new Pair<>(callingUid, callingPackage), bugreportFile);
+ mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile);
} catch (IllegalArgumentException e) {
Slog.e(TAG, e.getMessage());
reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
@@ -281,10 +329,17 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
// Wrap the listener so we can intercept binder events directly.
DumpstateListener myListener = new DumpstateListener(listener, ds,
new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);
+
+ boolean keepBugreportOnRetrieval = false;
+ if (onboardingBugreportV2Enabled()) {
+ keepBugreportOnRetrieval = mBugreportFileManager.mBugreportFilesToPersist.contains(
+ bugreportFile);
+ }
+
setCurrentDumpstateListenerLocked(myListener);
try {
- ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
- bugreportFile, myListener);
+ ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd,
+ bugreportFile, keepBugreportOnRetrieval, myListener);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException in retrieveBugreport", e);
}
@@ -317,7 +372,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private void validateBugreportFlags(int flags) {
flags = clearBugreportFlag(flags,
BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
- | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT);
+ | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT
+ | BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL);
if (flags != 0) {
Slog.w(TAG, "Unknown bugreport flags: " + flags);
throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
@@ -482,6 +538,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
boolean reportFinishedFile =
(bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
+ boolean keepBugreportOnRetrieval =
+ (bugreportFlags & BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL) != 0;
+
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
if (ds == null) {
Slog.w(TAG, "Unable to get bugreport service");
@@ -490,7 +549,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
DumpstateListener myListener = new DumpstateListener(listener, ds,
- new Pair<>(callingUid, callingPackage), reportFinishedFile);
+ new Pair<>(callingUid, callingPackage), reportFinishedFile,
+ keepBugreportOnRetrieval);
setCurrentDumpstateListenerLocked(myListener);
try {
ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
@@ -646,9 +706,16 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private final boolean mReportFinishedFile;
private int mProgress; // used for debugging purposes only
private boolean mDone;
+ private boolean mKeepBugreportOnRetrieval;
DumpstateListener(IDumpstateListener listener, IDumpstate ds,
Pair<Integer, String> caller, boolean reportFinishedFile) {
+ this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false);
+ }
+
+ DumpstateListener(IDumpstateListener listener, IDumpstate ds,
+ Pair<Integer, String> caller, boolean reportFinishedFile,
+ boolean keepBugreportOnRetrieval) {
if (DEBUG) {
Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
}
@@ -656,6 +723,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
mDs = ds;
mCaller = caller;
mReportFinishedFile = reportFinishedFile;
+ mKeepBugreportOnRetrieval = keepBugreportOnRetrieval;
try {
mDs.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -690,7 +758,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
reportFinishedLocked("File: " + bugreportFile);
}
if (mReportFinishedFile) {
- mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+ mBugreportFileManager.addBugreportFileForCaller(
+ mCaller, bugreportFile, mKeepBugreportOnRetrieval);
} else if (DEBUG) {
Slog.d(TAG, "Not reporting finished file");
}
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index fc27edcb7bda..a4d50f0f9559 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -94,27 +94,31 @@ public class BugreportManagerServiceImplTest {
public void testBugreportFileManagerFileExists() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
- callingInfo, mBugreportFile);
+ callingInfo, mBugreportFile, /* keepOnRetrieval= */ false);
assertThrows(IllegalArgumentException.class, () ->
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- callingInfo, "unknown-file.zip"));
+ mContext, callingInfo, Process.myUserHandle().getIdentifier(),
+ "unknown-file.zip"));
// No exception should be thrown.
- mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ mContext, callingInfo, mContext.getUserId(), mBugreportFile);
}
@Test
public void testBugreportFileManagerMultipleFiles() {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
mBugreportFileManager.addBugreportFileForCaller(
- callingInfo, mBugreportFile);
+ callingInfo, mBugreportFile, /* keepOnRetrieval= */ false);
mBugreportFileManager.addBugreportFileForCaller(
- callingInfo, mBugreportFile2);
+ callingInfo, mBugreportFile2, /* keepOnRetrieval= */ false);
// No exception should be thrown.
- mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile);
- mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile2);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ mContext, callingInfo, mContext.getUserId(), mBugreportFile);
+ mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
+ mContext, callingInfo, mContext.getUserId(), mBugreportFile2);
}
@Test
@@ -122,7 +126,8 @@ public class BugreportManagerServiceImplTest {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
assertThrows(IllegalArgumentException.class,
() -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- callingInfo, "test-file.zip"));
+ mContext, callingInfo, Process.myUserHandle().getIdentifier(),
+ "test-file.zip"));
}
@Test
@@ -130,7 +135,8 @@ public class BugreportManagerServiceImplTest {
CountDownLatch latch = new CountDownLatch(1);
Listener listener = new Listener(latch);
mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(),
- new FileDescriptor(), mBugreportFile, listener);
+ mContext.getUserId(), new FileDescriptor(), mBugreportFile,
+ /* keepOnRetrieval= */ false, listener);
assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
assertThat(listener.getErrorCode()).isEqualTo(
BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);