Merge "Add retrieveBugreport API"
This commit is contained in:
committed by
Android (Google) Code Review
commit
34fb4ac107
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */,
|
||||
@@ -237,6 +266,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.
|
||||
*
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -677,7 +677,7 @@ public class BugreportReceiverTest {
|
||||
if (mScreenshotFd != null) {
|
||||
writeScreenshotFile(mScreenshotFd, SCREENSHOT_CONTENT);
|
||||
}
|
||||
mIDumpstateListener.onFinished();
|
||||
mIDumpstateListener.onFinished("");
|
||||
getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
BugreportManagerServiceImpl(Context context) {
|
||||
mContext = context;
|
||||
mAppOps = context.getSystemService(AppOpsManager.class);
|
||||
mTelephonyManager = context.getSystemService(TelephonyManager.class);
|
||||
mBugreportWhitelistedPackages =
|
||||
SystemConfig.getInstance().getBugreportWhitelistedPackages();
|
||||
/** 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) {
|
||||
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 =
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# Component 153446
|
||||
include /platform/frameworks/native:/cmds/dumpstate/OWNERS
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "BugreportManagerServiceImplTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "com.android.server.os."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user