diff options
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." + } + ] + } + ] +} |