Merge "Handle legacy broadcasts from dumpstate logic in onFinished() callback"
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 602fe3e..2a41aa6 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -37,6 +37,7 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.app.admin.DevicePolicyManager;
import android.content.ClipData;
import android.content.Context;
import android.content.DialogInterface;
@@ -107,6 +108,8 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -157,6 +160,8 @@
private static final String TAG = "BugreportProgressService";
private static final boolean DEBUG = false;
+ private Intent startSelfIntent;
+
private static final String AUTHORITY = "com.android.shell";
// External intents sent by dumpstate.
@@ -235,6 +240,24 @@
private static final String NOTIFICATION_CHANNEL_ID = "bugreports";
+ /**
+ * Always keep the newest 8 bugreport files.
+ */
+ private static final int MIN_KEEP_COUNT = 8;
+
+ /**
+ * Always keep bugreports taken in the last week.
+ */
+ private static final long MIN_KEEP_AGE = DateUtils.WEEK_IN_MILLIS;
+
+ private static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
+
+ /** Always keep just the last 3 remote bugreport's files around. */
+ private static final int REMOTE_BUGREPORT_FILES_AMOUNT = 3;
+
+ /** Always keep remote bugreport files created in the last day. */
+ private static final long REMOTE_MIN_KEEP_AGE = DateUtils.DAY_IN_MILLIS;
+
private final Object mLock = new Object();
/** Managed bugreport info (keyed by id) */
@@ -281,6 +304,7 @@
mMainThreadHandler = new Handler(Looper.getMainLooper());
mServiceHandler = new ServiceHandler("BugreportProgressServiceMainThread");
mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
+ startSelfIntent = new Intent(this, this.getClass());
mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR);
if (!mScreenshotsDir.exists()) {
@@ -307,6 +331,9 @@
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "onStartCommand(): " + dumpIntent(intent));
if (intent != null) {
+ if (!intent.hasExtra(EXTRA_ORIGINAL_INTENT) && !intent.hasExtra(EXTRA_ID)) {
+ return START_NOT_STICKY;
+ }
// Handle it in a separate thread.
final Message msg = mServiceHandler.obtainMessage();
msg.what = MSG_SERVICE_COMMAND;
@@ -352,10 +379,11 @@
private final BugreportInfo mInfo;
- BugreportCallbackImpl(String name, @Nullable String title, @Nullable String description) {
+ BugreportCallbackImpl(String name, @Nullable String title, @Nullable String description,
+ @BugreportParams.BugreportMode int type) {
// pid not used in this workflow, so setting default = 0
mInfo = new BugreportInfo(mContext, 0 /* pid */, name,
- 100 /* max progress*/, title, description);
+ 100 /* max progress*/, title, description, type);
}
@Override
@@ -380,10 +408,9 @@
@Override
public void onFinished() {
+ // TODO: Make all callback functions lock protected.
trackInfoWithId();
- // Stop running on foreground, otherwise share notification cannot be dismissed.
- onBugreportFinished(mInfo.id);
- stopSelfWhenDone();
+ sendBugreportFinishedBroadcast();
}
/**
@@ -400,6 +427,90 @@
}
return;
}
+
+ private void sendBugreportFinishedBroadcast() {
+ final String bugreportFileName = mInfo.name + ".zip";
+ final File bugreportFile = new File(BUGREPORT_DIR, bugreportFileName);
+ final String bugreportFilePath = bugreportFile.getAbsolutePath();
+ if (bugreportFile.length() == 0) {
+ Log.e(TAG, "Bugreport file empty. File path = " + bugreportFilePath);
+ return;
+ }
+ if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE) {
+ sendRemoteBugreportFinishedBroadcast(bugreportFilePath, bugreportFile);
+ } else {
+ cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE);
+ final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
+ intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
+ addScreenshotToIntent(intent);
+ mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
+ onBugreportFinished(mInfo.id);
+ }
+ }
+
+ private void sendRemoteBugreportFinishedBroadcast(String bugreportFileName,
+ File bugreportFile) {
+ cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE);
+ final Intent intent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH);
+ final Uri bugreportUri = getUri(mContext, bugreportFile);
+ final String bugreportHash = generateFileHash(bugreportFileName);
+ if (bugreportHash == null) {
+ Log.e(TAG, "Error generating file hash for remote bugreport");
+ return;
+ }
+ intent.setDataAndType(bugreportUri, BUGREPORT_MIMETYPE);
+ intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
+ intent.putExtra(EXTRA_BUGREPORT, bugreportFileName);
+ mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM,
+ android.Manifest.permission.DUMP);
+ }
+
+ private void addScreenshotToIntent(Intent intent) {
+ final String screenshotFileName = mInfo.name + ".png";
+ final File screenshotFile = new File(BUGREPORT_DIR, screenshotFileName);
+ final String screenshotFilePath = screenshotFile.getAbsolutePath();
+ if (screenshotFile.length() > 0) {
+ intent.putExtra(EXTRA_SCREENSHOT, screenshotFilePath);
+ }
+ return;
+ }
+
+ private String generateFileHash(String fileName) {
+ String fileHash = null;
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ FileInputStream input = new FileInputStream(new File(fileName));
+ byte[] buffer = new byte[65536];
+ int size;
+ while ((size = input.read(buffer)) > 0) {
+ md.update(buffer, 0, size);
+ }
+ input.close();
+ byte[] hashBytes = md.digest();
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < hashBytes.length; i++) {
+ sb.append(String.format("%02x", hashBytes[i]));
+ }
+ fileHash = sb.toString();
+ } catch (IOException | NoSuchAlgorithmException e) {
+ Log.e(TAG, "generating file hash for bugreport file failed " + fileName, e);
+ }
+ return fileHash;
+ }
+ }
+
+ static void cleanupOldFiles(final int minCount, final long minAge) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ FileUtils.deleteOlderFiles(new File(BUGREPORT_DIR), minCount, minAge);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "RuntimeException deleting old files", e);
+ }
+ return null;
+ }
+ }.execute();
}
/**
@@ -598,7 +709,7 @@
+ " screenshot file fd: " + screenshotFd);
BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(bugreportName,
- shareTitle, shareDescription);
+ shareTitle, shareDescription, bugreportType);
try {
mBugreportManager.startBugreport(bugreportFd, screenshotFd,
new BugreportParams(bugreportType), executor, bugreportCallback);
@@ -711,6 +822,9 @@
} else {
mForegroundId = id;
Log.d(TAG, "Start running as foreground service on id " + mForegroundId);
+ // Explicitly starting the service so that stopForeground() does not crash
+ // Workaround for b/140997620
+ startForegroundService(startSelfIntent);
startForeground(mForegroundId, notification);
}
}
@@ -1925,10 +2039,19 @@
String shareDescription;
/**
+ * Type of the bugreport
+ */
+ int type;
+
+ /**
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
*/
BugreportInfo(Context context, int id, int pid, String name, int max) {
- this(context, pid, name, max, null, null);
+ // bugreports triggered by STARTED broadcast do not use callback functions,
+ // onFinished() callback method is the only function where type is used.
+ // Set type to -1 as it is unused in this workflow.
+ // This constructor will soon be removed.
+ this(context, pid, name, max, null, null, -1);
this.id = id;
}
@@ -1936,13 +2059,14 @@
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
*/
BugreportInfo(Context context, int pid, String name, int max, @Nullable String shareTitle,
- @Nullable String shareDescription) {
+ @Nullable String shareDescription, int type) {
this.context = context;
this.pid = pid;
this.name = name;
this.max = this.realMax = max;
this.shareTitle = shareTitle == null ? "" : shareTitle;
this.shareDescription = shareDescription == null ? "" : shareDescription;
+ this.type = type;
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c46738d..7b69bea 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8311,6 +8311,8 @@
triggerShellBugreport.setAction(INTENT_BUGREPORT_REQUESTED);
triggerShellBugreport.setPackage(SHELL_APP_PACKAGE);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType);
+ triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
if (shareTitle != null) {
triggerShellBugreport.putExtra(EXTRA_TITLE, shareTitle);
}