Loading core/api/system-current.txt +4 −0 Original line number Diff line number Diff line Loading @@ -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 core/java/android/os/BugreportManager.java +24 −0 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -198,6 +221,7 @@ public final class BugreportManager { bugreportFd.getFileDescriptor(), screenshotFd.getFileDescriptor(), params.getMode(), params.getFlags(), dsListener, isScreenshotRequested); } catch (RemoteException e) { Loading core/java/android/os/BugreportParams.java +47 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; } core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java +245 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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); Loading @@ -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; Loading Loading @@ -124,7 +150,6 @@ public class BugreportManagerTest { FileUtils.closeQuietly(mScreenshotFd); } @Test public void normalFlow_wifi() throws Exception { BugreportCallbackImpl callback = new BugreportCallbackImpl(); Loading Loading @@ -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 Loading Loading @@ -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(); Loading @@ -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; } } Loading @@ -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()) { Loading Loading @@ -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, Loading packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +4 −4 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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(); } Loading Loading @@ -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); Loading Loading
core/api/system-current.txt +4 −0 Original line number Diff line number Diff line Loading @@ -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
core/java/android/os/BugreportManager.java +24 −0 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -198,6 +221,7 @@ public final class BugreportManager { bugreportFd.getFileDescriptor(), screenshotFd.getFileDescriptor(), params.getMode(), params.getFlags(), dsListener, isScreenshotRequested); } catch (RemoteException e) { Loading
core/java/android/os/BugreportParams.java +47 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; }
core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java +245 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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); Loading @@ -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; Loading Loading @@ -124,7 +150,6 @@ public class BugreportManagerTest { FileUtils.closeQuietly(mScreenshotFd); } @Test public void normalFlow_wifi() throws Exception { BugreportCallbackImpl callback = new BugreportCallbackImpl(); Loading Loading @@ -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 Loading Loading @@ -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(); Loading @@ -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; } } Loading @@ -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()) { Loading Loading @@ -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, Loading
packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +4 −4 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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(); } Loading Loading @@ -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); Loading