Merge "Add bugreport pre-dump functionality"

This commit is contained in:
Kean Mariotti
2022-10-14 06:54:01 +00:00
committed by Android (Google) Code Review
6 changed files with 419 additions and 34 deletions

View File

@@ -9276,13 +9276,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

View File

@@ -147,6 +147,29 @@ public final class BugreportManager {
public void onEarlyReportFinished() {}
}
/**
* 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.
*
@@ -198,6 +221,7 @@ public final class BugreportManager {
bugreportFd.getFileDescriptor(),
screenshotFd.getFileDescriptor(),
params.getMode(),
params.getFlags(),
dsListener,
isScreenshotRequested);
} catch (RemoteException e) {

View File

@@ -30,15 +30,45 @@ 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;
}

View File

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

View File

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

View File

@@ -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);
@@ -68,15 +72,27 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
SystemConfig.getInstance().getBugreportWhitelistedPackages();
}
@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);
@@ -223,18 +244,63 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
return true;
}
@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);