diff options
6 files changed, 419 insertions, 34 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7fd4283ca42e..3de74a41f1e4 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9300,13 +9300,17 @@ 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 startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback); } public final class BugreportParams { ctor public BugreportParams(int); + ctor public BugreportParams(int, int); + method public int getFlags(); method public int getMode(); + 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 field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2 diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 73bb8d566500..222e88f9d3e1 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -148,6 +148,29 @@ public final class BugreportManager { } /** + * Speculatively pre-dumps UI data for a bugreport request that might come later. + * + * <p>Triggers the dump of certain critical UI data, e.g. traces stored in short + * ring buffers that might get lost by the time the actual bugreport is requested. + * + * <p>{@link #startBugreport} will then pick the pre-dumped data if both of the following + * conditions are met: + * - {@link android.os.BugreportParams#BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified. + * - {@link #preDumpUiData} and {@link #startBugreport} were called by the same UID. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.DUMP) + @WorkerThread + public void preDumpUiData() { + try { + mBinder.preDumpUiData(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Starts a bugreport. * * <p>This starts a bugreport in the background. However the call itself can take several @@ -198,6 +221,7 @@ public final class BugreportManager { bugreportFd.getFileDescriptor(), screenshotFd.getFileDescriptor(), params.getMode(), + params.getFlags(), dsListener, isScreenshotRequested); } catch (RemoteException e) { diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index 279ccae7c94f..990883f6d83c 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -30,16 +30,46 @@ import java.lang.annotation.RetentionPolicy; @SystemApi public final class BugreportParams { private final int mMode; + private final int mFlags; + /** + * Constructs a BugreportParams object to specify what kind of bugreport should be taken. + * + * @param mode of the bugreport to request + */ public BugreportParams(@BugreportMode int mode) { mMode = mode; + mFlags = 0; } + /** + * Constructs a BugreportParams object to specify what kind of bugreport should be taken. + * + * @param mode of the bugreport to request + * @param flags to customize the bugreport request + */ + public BugreportParams(@BugreportMode int mode, @BugreportFlag int flags) { + mMode = mode; + mFlags = flags; + } + + /** + * Returns the mode of the bugreport to request. + */ + @BugreportMode public int getMode() { return mMode; } /** + * Returns the flags to customize the bugreport request. + */ + @BugreportFlag + public int getFlags() { + return mFlags; + } + + /** * Defines acceptable types of bugreports. * @hide */ @@ -88,4 +118,21 @@ public final class BugreportParams { * Wifi. */ public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI; + + /** + * Defines acceptable flags for customizing bugreport requests. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = { + BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA + }) + public @interface BugreportFlag {} + + /** + * Flag for reusing pre-dumped UI data. The pre-dump and bugreport request calls must be + * performed by the same UID, otherwise the flag is ignored. + */ + public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = + IDumpstate.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA; } diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java index daae9579cabe..180a312a3f2b 100644 --- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java +++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java @@ -36,7 +36,6 @@ import android.os.HandlerThread; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.StrictMode; -import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; @@ -48,6 +47,9 @@ import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -57,11 +59,22 @@ import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; /** * Tests for BugreportManager API. @@ -74,6 +87,7 @@ public class BugreportManagerTest { private static final String TAG = "BugreportManagerTest"; private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10); private static final long DUMPSTATE_STARTUP_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); + private static final long DUMPSTATE_TEARDOWN_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); @@ -89,6 +103,18 @@ public class BugreportManagerTest { private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT"; private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT"; + private static final Path[] UI_TRACES_PREDUMPED = { + Paths.get("/data/misc/wmtrace/ime_trace_clients.winscope"), + Paths.get("/data/misc/wmtrace/ime_trace_managerservice.winscope"), + Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"), + Paths.get("/data/misc/wmtrace/wm_trace.winscope"), + Paths.get("/data/misc/wmtrace/layers_trace.winscope"), + Paths.get("/data/misc/wmtrace/transactions_trace.winscope"), + }; + private static final Path[] UI_TRACES_GENERATED_DURING_BUGREPORT = { + Paths.get("/data/misc/wmtrace/layers_trace_from_transactions.winscope"), + }; + private Handler mHandler; private Executor mExecutor; private BugreportManager mBrm; @@ -124,7 +150,6 @@ public class BugreportManagerTest { FileUtils.closeQuietly(mScreenshotFd); } - @Test public void normalFlow_wifi() throws Exception { BugreportCallbackImpl callback = new BugreportCallbackImpl(); @@ -175,6 +200,66 @@ public class BugreportManagerTest { assertFdsAreClosed(mBugreportFd, mScreenshotFd); } + @LargeTest + @Test + public void preDumpUiData_then_fullWithUsePreDumpFlag() throws Exception { + startPreDumpedUiTraces(); + + mBrm.preDumpUiData(); + waitTillDumpstateExitedOrTimeout(); + List<File> expectedPreDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED); + + BugreportCallbackImpl callback = new BugreportCallbackImpl(); + mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor, + callback); + shareConsentDialog(ConsentReply.ALLOW); + waitTillDoneOrTimeout(callback); + + stopPreDumpedUiTraces(); + + assertThat(callback.isDone()).isTrue(); + assertThat(mBugreportFile.length()).isGreaterThan(0L); + assertFdsAreClosed(mBugreportFd); + + assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED); + assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT); + + List<File> actualPreDumpedTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED); + assertThatAllFileContentsAreEqual(actualPreDumpedTraceFiles, expectedPreDumpedTraceFiles); + } + + @LargeTest + @Test + public void preDumpData_then_fullWithoutUsePreDumpFlag_ignoresPreDump() throws Exception { + startPreDumpedUiTraces(); + + // Simulate pre-dump, instead of taking a real one. + // In some corner cases, data dumped as part of the full bugreport could be the same as the + // pre-dumped data and this test would fail. Hence, here we create fake/artificial + // pre-dumped data that we know it won't match with the full bugreport data. + createFilesWithFakeDataAsRoot(UI_TRACES_PREDUMPED, "system"); + + List<File> preDumpedTraceFiles = copyFilesAsRoot(UI_TRACES_PREDUMPED); + + BugreportCallbackImpl callback = new BugreportCallbackImpl(); + mBrm.startBugreport(mBugreportFd, null, full(), mExecutor, + callback); + shareConsentDialog(ConsentReply.ALLOW); + waitTillDoneOrTimeout(callback); + + stopPreDumpedUiTraces(); + + assertThat(callback.isDone()).isTrue(); + assertThat(mBugreportFile.length()).isGreaterThan(0L); + assertFdsAreClosed(mBugreportFd); + + assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED); + assertThatBugreportContainsFiles(UI_TRACES_GENERATED_DURING_BUGREPORT); + + List<File> actualTraceFiles = extractFilesFromBugreport(UI_TRACES_PREDUMPED); + assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles); + } + @Test public void simultaneousBugreportsNotAllowed() throws Exception { // Start bugreport #1 @@ -384,21 +469,151 @@ public class BugreportManagerTest { } return bm; } - private static File createTempFile(String prefix, String extension) throws Exception { final File f = File.createTempFile(prefix, extension); f.setReadable(true, true); f.setWritable(true, true); - f.deleteOnExit(); return f; } + private static void startPreDumpedUiTraces() { + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "cmd input_method tracing start" + ); + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "cmd window tracing start" + ); + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "service call SurfaceFlinger 1025 i32 1" + ); + } + + private static void stopPreDumpedUiTraces() { + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "cmd input_method tracing stop" + ); + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "cmd window tracing stop" + ); + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "service call SurfaceFlinger 1025 i32 0" + ); + } + + private void assertThatBugreportContainsFiles(Path[] paths) + throws IOException { + List<Path> entries = listZipArchiveEntries(mBugreportFile); + for (Path pathInDevice : paths) { + Path pathInArchive = Paths.get("FS" + pathInDevice.toString()); + assertThat(entries).contains(pathInArchive); + } + } + + private List<File> extractFilesFromBugreport(Path[] paths) throws Exception { + List<File> files = new ArrayList<File>(); + for (Path pathInDevice : paths) { + Path pathInArchive = Paths.get("FS" + pathInDevice.toString()); + files.add(extractZipArchiveEntry(mBugreportFile, pathInArchive)); + } + return files; + } + + private static List<Path> listZipArchiveEntries(File archive) throws IOException { + ArrayList<Path> entries = new ArrayList<>(); + + ZipInputStream stream = new ZipInputStream( + new BufferedInputStream(new FileInputStream(archive))); + + for (ZipEntry entry = stream.getNextEntry(); entry != null; entry = stream.getNextEntry()) { + entries.add(Paths.get(entry.toString())); + } + + return entries; + } + + private static File extractZipArchiveEntry(File archive, Path entryToExtract) + throws Exception { + File extractedFile = createTempFile(entryToExtract.getFileName().toString(), ".extracted"); + + ZipInputStream is = new ZipInputStream(new FileInputStream(archive)); + boolean hasFoundEntry = false; + + for (ZipEntry entry = is.getNextEntry(); entry != null; entry = is.getNextEntry()) { + if (entry.toString().equals(entryToExtract.toString())) { + BufferedOutputStream os = + new BufferedOutputStream(new FileOutputStream(extractedFile)); + ByteStreams.copy(is, os); + os.close(); + hasFoundEntry = true; + break; + } + + ByteStreams.exhaust(is); // skip entry + } + + is.closeEntry(); + is.close(); + + assertThat(hasFoundEntry).isTrue(); + + return extractedFile; + } + + private static void createFilesWithFakeDataAsRoot(Path[] paths, String owner) throws Exception { + File src = createTempFile("fake", ".data"); + Files.write("fake data".getBytes(StandardCharsets.UTF_8), src); + + for (Path path : paths) { + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "install -m 611 -o " + owner + " -g " + owner + + " " + src.getAbsolutePath() + " " + path.toString() + ); + } + } + + private static List<File> copyFilesAsRoot(Path[] paths) throws Exception { + ArrayList<File> files = new ArrayList<File>(); + for (Path src : paths) { + File dst = createTempFile(src.getFileName().toString(), ".copy"); + InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand( + "cp " + src.toString() + " " + dst.getAbsolutePath() + ); + files.add(dst); + } + return files; + } + private static ParcelFileDescriptor parcelFd(File file) throws Exception { return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND); } + private static void assertThatAllFileContentsAreEqual(List<File> actual, List<File> expected) + throws IOException { + if (actual.size() != expected.size()) { + fail("File lists have different size"); + } + for (int i = 0; i < actual.size(); ++i) { + if (!Files.equal(actual.get(i), expected.get(i))) { + fail("Contents of " + actual.get(i).toString() + + " != " + expected.get(i).toString()); + } + } + } + + private static void assertThatAllFileContentsAreDifferent(List<File> a, List<File> b) + throws IOException { + if (a.size() != b.size()) { + fail("File lists have different size"); + } + for (int i = 0; i < a.size(); ++i) { + if (Files.equal(a.get(i), b.get(i))) { + fail("Contents of " + a.get(i).toString() + " == " + b.get(i).toString()); + } + } + } + private static void dropPermissions() { InstrumentationRegistry.getInstrumentation().getUiAutomation() .dropShellPermissionIdentity(); @@ -410,21 +625,16 @@ public class BugreportManagerTest { } private static boolean isDumpstateRunning() { - String[] output; + String output; try { - output = - UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - .executeShellCommand("ps -A -o NAME | grep dumpstate") - .trim() - .split("\n"); + output = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + .executeShellCommand("service list | grep dumpstate"); } catch (IOException e) { Log.w(TAG, "Failed to check if dumpstate is running", e); return false; } - for (String line : output) { - // Check for an exact match since there may be other things that contain "dumpstate" as - // a substring (e.g. the dumpstate HAL). - if (TextUtils.equals("dumpstate", line)) { + for (String line : output.trim().split("\n")) { + if (line.matches("^.*\\s+dumpstate:\\s+\\[.*\\]$")) { return true; } } @@ -449,6 +659,17 @@ public class BugreportManagerTest { return System.currentTimeMillis(); } + private static void waitTillDumpstateExitedOrTimeout() throws Exception { + long startTimeMs = now(); + while (isDumpstateRunning()) { + Thread.sleep(500 /* .5s */); + if (now() - startTimeMs >= DUMPSTATE_TEARDOWN_TIMEOUT_MS) { + break; + } + Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms for dumpstate to exit"); + } + } + private static void waitTillDumpstateRunningOrTimeout() throws Exception { long startTimeMs = now(); while (!isDumpstateRunning()) { @@ -500,6 +721,16 @@ public class BugreportManagerTest { return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL); } + /* + * Returns a {@link BugreportParams} for full bugreport that reuses pre-dumped data. + * + * <p> This can take on the order of minutes to finish + */ + private static BugreportParams fullWithUsePreDumpFlag() { + return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL, + BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA); + } + /* Allow/deny the consent dialog to sharing bugreport data or check existence only. */ private enum ConsentReply { ALLOW, diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index 947691206741..cb37c079d2d0 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -199,8 +199,8 @@ public class BugreportReceiverTest { } mBugreportFd = ParcelFileDescriptor.dup(invocation.getArgument(2)); return null; - }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), any(), - anyBoolean()); + }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(), + any(), anyBoolean()); setWarningState(mContext, STATE_HIDE); @@ -543,7 +543,7 @@ public class BugreportReceiverTest { getInstrumentation().waitForIdleSync(); verify(mMockIDumpstate, times(1)).startBugreport(anyInt(), any(), any(), any(), - anyInt(), any(), anyBoolean()); + anyInt(), anyInt(), any(), anyBoolean()); sendBugreportFinished(); } @@ -608,7 +608,7 @@ public class BugreportReceiverTest { ArgumentCaptor<IDumpstateListener> listenerCap = ArgumentCaptor.forClass( IDumpstateListener.class); verify(mMockIDumpstate, timeout(TIMEOUT)).startBugreport(anyInt(), any(), any(), any(), - anyInt(), listenerCap.capture(), anyBoolean()); + anyInt(), anyInt(), listenerCap.capture(), anyBoolean()); mIDumpstateListener = listenerCap.getValue(); assertNotNull("Dumpstate listener should not be null", mIDumpstateListener); mIDumpstateListener.onProgress(0); diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 346f3113f25f..58428ca48c97 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -43,6 +43,7 @@ import com.android.server.SystemConfig; import java.io.FileDescriptor; import java.util.Objects; +import java.util.OptionalInt; /** * Implementation of the service that provides a privileged API to capture and consume bugreports. @@ -60,6 +61,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private final TelephonyManager mTelephonyManager; private final ArraySet<String> mBugreportWhitelistedPackages; + @GuardedBy("mLock") + private OptionalInt mPreDumpedDataUid = OptionalInt.empty(); + BugreportManagerServiceImpl(Context context) { mContext = context; mAppOps = context.getSystemService(AppOpsManager.class); @@ -70,13 +74,25 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { @Override @RequiresPermission(android.Manifest.permission.DUMP) + public void preDumpUiData(String callingPackage) { + enforcePermission(callingPackage, Binder.getCallingUid(), true); + + synchronized (mLock) { + preDumpUiDataLocked(callingPackage); + } + } + + @Override + @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(int callingUidUnused, String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, - int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) { + int bugreportMode, int bugreportFlags, IDumpstateListener listener, + boolean isScreenshotRequested) { Objects.requireNonNull(callingPackage); Objects.requireNonNull(bugreportFd); Objects.requireNonNull(listener); validateBugreportMode(bugreportMode); + validateBugreportFlags(bugreportFlags); int callingUid = Binder.getCallingUid(); enforcePermission(callingPackage, callingUid, bugreportMode @@ -90,7 +106,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { synchronized (mLock) { startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd, - bugreportMode, listener, isScreenshotRequested); + bugreportMode, bugreportFlags, listener, isScreenshotRequested); } } @@ -108,17 +124,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } try { // Note: this may throw SecurityException back out to the caller if they aren't - // allowed to cancel the report, in which case we should NOT be setting ctl.stop, - // since that would unintentionally kill some other app's bugreport, which we - // specifically disallow. + // allowed to cancel the report, in which case we should NOT stop the dumpstate + // service, since that would unintentionally kill some other app's bugreport, which + // we specifically disallow. ds.cancelBugreport(callingUid, callingPackage); } catch (RemoteException e) { Slog.e(TAG, "RemoteException in cancelBugreport", e); } - // This tells init to cancel bugreportd service. Note that this is achieved through - // setting a system property which is not thread-safe. So the lock here offers - // thread-safety only among callers of the API. - SystemProperties.set("ctl.stop", BUGREPORT_SERVICE); + stopDumpstateBinderServiceLocked(); } } @@ -134,6 +147,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } } + private void validateBugreportFlags(int flags) { + flags = clearBugreportFlag(flags, BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA); + if (flags != 0) { + Slog.w(TAG, "Unknown bugreport flags: " + flags); + throw new IllegalArgumentException("Unknown bugreport flags: " + flags); + } + } + private void enforcePermission( String callingPackage, int callingUid, boolean checkCarrierPrivileges) { mAppOps.checkPackage(callingUid, callingPackage); @@ -224,17 +245,62 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } @GuardedBy("mLock") + private void preDumpUiDataLocked(String callingPackage) { + mPreDumpedDataUid = OptionalInt.empty(); + + if (isDumpstateBinderServiceRunningLocked()) { + Slog.e(TAG, "'dumpstate' is already running. " + + "Cannot pre-dump data while another operation is currently in progress."); + return; + } + + IDumpstate ds = startAndGetDumpstateBinderServiceLocked(); + if (ds == null) { + Slog.e(TAG, "Unable to get bugreport service"); + return; + } + + try { + ds.preDumpUiData(callingPackage); + } catch (RemoteException e) { + return; + } finally { + // dumpstate service is already started now. We need to kill it to manage the + // lifecycle correctly. If we don't subsequent callers will get + // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error. + stopDumpstateBinderServiceLocked(); + } + + + mPreDumpedDataUid = OptionalInt.of(Binder.getCallingUid()); + } + + @GuardedBy("mLock") private void startBugreportLocked(int callingUid, String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, - int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) { + int bugreportMode, int bugreportFlags, IDumpstateListener listener, + boolean isScreenshotRequested) { if (isDumpstateBinderServiceRunningLocked()) { Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport" - + " while another one is currently in progress."); - reportError(listener, - IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS); + + " while another operation is currently in progress."); + reportError(listener, IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS); return; } + if ((bugreportFlags & BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA) != 0) { + if (mPreDumpedDataUid.isEmpty()) { + bugreportFlags = clearBugreportFlag(bugreportFlags, + BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA); + Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA." + + " No pre-dumped data is available."); + } else if (mPreDumpedDataUid.getAsInt() != callingUid) { + bugreportFlags = clearBugreportFlag(bugreportFlags, + BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA); + Slog.w(TAG, "Ignoring BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA." + + " Data was pre-dumped by a different UID."); + } + } + IDumpstate ds = startAndGetDumpstateBinderServiceLocked(); if (ds == null) { Slog.w(TAG, "Unable to get bugreport service"); @@ -245,10 +311,10 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { // Wrap the listener so we can intercept binder events directly. IDumpstateListener myListener = new DumpstateListener(listener, ds); try { - ds.startBugreport(callingUid, callingPackage, - bugreportFd, screenshotFd, bugreportMode, myListener, isScreenshotRequested); + ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode, + bugreportFlags, myListener, isScreenshotRequested); } catch (RemoteException e) { - // bugreportd service is already started now. We need to kill it to manage the + // dumpstate service is already started now. We need to kill it to manage the // lifecycle correctly. If we don't subsequent callers will get // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error. // Note that listener will be notified by the death recipient below. @@ -309,6 +375,19 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { return ds; } + @GuardedBy("mLock") + private void stopDumpstateBinderServiceLocked() { + // This tells init to cancel bugreportd service. Note that this is achieved through + // setting a system property which is not thread-safe. So the lock here offers + // thread-safety only among callers of the API. + SystemProperties.set("ctl.stop", BUGREPORT_SERVICE); + } + + private int clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag) { + flags &= ~flag; + return flags; + } + private void reportError(IDumpstateListener listener, int errorCode) { try { listener.onError(errorCode); |