Merge "Add retrieveBugreport API"

This commit is contained in:
Gavin Corkery
2023-02-01 19:54:31 +00:00
committed by Android (Google) Code Review
9 changed files with 441 additions and 23 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -677,7 +677,7 @@ public class BugreportReceiverTest {
if (mScreenshotFd != null) {
writeScreenshotFile(mScreenshotFd, SCREENSHOT_CONTENT);
}
mIDumpstateListener.onFinished();
mIDumpstateListener.onFinished("");
getInstrumentation().waitForIdleSync();
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,2 @@
# Component 153446
include /platform/frameworks/native:/cmds/dumpstate/OWNERS

View File

@@ -0,0 +1,12 @@
{
"presubmit": [
{
"name": "BugreportManagerServiceImplTests",
"options": [
{
"include-filter": "com.android.server.os."
}
]
}
]
}