summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/system-current.txt4
-rw-r--r--core/java/android/os/BugreportManager.java24
-rw-r--r--core/java/android/os/BugreportParams.java47
-rw-r--r--core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java259
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java8
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java111
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);