summaryrefslogtreecommitdiff
path: root/packages/Shell/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/Shell/src')
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java1210
-rw-r--r--packages/Shell/src/com/android/shell/BugreportReceiver.java88
-rw-r--r--packages/Shell/src/com/android/shell/BugreportRequestedReceiver.java47
-rw-r--r--packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java67
4 files changed, 745 insertions, 667 deletions
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 520e27bcecf9..02815a571db8 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -24,40 +24,12 @@ import static com.android.shell.BugreportPrefs.STATE_HIDE;
import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
-
-import libcore.io.Streams;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.ChooserActivity;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.FastPrintWriter;
-
-import com.google.android.collect.Lists;
-
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.app.ActivityThread;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.Notification.Action;
@@ -65,6 +37,7 @@ import android.app.NotificationChannel;
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;
@@ -74,25 +47,25 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.BugreportManager;
+import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportManager.BugreportCallback.BugreportErrorCode;
+import android.os.BugreportParams;
import android.os.Bundle;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.IDumpstate;
-import android.os.IDumpstateListener;
-import android.os.IDumpstateToken;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
-import androidx.core.content.FileProvider;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
@@ -103,51 +76,75 @@ import android.view.ContextThemeWrapper;
import android.view.IWindowManager;
import android.view.View;
import android.view.WindowManager;
-import android.view.View.OnFocusChangeListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
+import androidx.core.content.FileProvider;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ChooserActivity;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import com.google.android.collect.Lists;
+
+import libcore.io.Streams;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+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;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
/**
- * Service used to keep progress of bugreport processes ({@code dumpstate}).
+ * Service used to trigger system bugreports.
* <p>
- * The workflow is:
- * <ol>
- * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with a sequential id,
- * its pid, and the estimated total effort.
- * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
- * <li>Upon start, this service:
+ * The workflow uses Bugreport API({@code BugreportManager}) and is as follows:
* <ol>
- * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
- * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
- * <li>If the progress changed, it updates the system notification.
- * </ol>
- * <li>As {@code dumpstate} progresses, it updates the system property.
- * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
- * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
- * turn:
- * <ol>
- * <li>Updates the system notification so user can share the bugreport.
- * <li>Stops monitoring that {@code dumpstate} process.
- * <li>Stops itself if it doesn't have any process left to monitor.
- * </ol>
+ * <li>System apps like Settings or SysUI broadcasts {@code BUGREPORT_REQUESTED}.
+ * <li>{@link BugreportRequestedReceiver} receives the intent and delegates it to this service.
+ * <li>This service calls startBugreport() and passes in local file descriptors to receive
+ * bugreport artifacts.
* </ol>
- *
- * TODO: There are multiple threads involved. Add synchronization accordingly.
*/
public class BugreportProgressService extends Service {
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.
- static final String INTENT_BUGREPORT_STARTED =
- "com.android.internal.intent.action.BUGREPORT_STARTED";
+ // External intent used to trigger bugreport API.
+ static final String INTENT_BUGREPORT_REQUESTED =
+ "com.android.internal.intent.action.BUGREPORT_REQUESTED";
+
+ // Intent sent to notify external apps that bugreport finished
static final String INTENT_BUGREPORT_FINISHED =
"com.android.internal.intent.action.BUGREPORT_FINISHED";
- static final String INTENT_REMOTE_BUGREPORT_FINISHED =
- "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED";
// Internal intents used on notification actions.
static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
@@ -158,10 +155,9 @@ public class BugreportProgressService extends Service {
"android.intent.action.BUGREPORT_SCREENSHOT";
static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
+ static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
static final String EXTRA_ID = "android.intent.extra.ID";
- static final String EXTRA_PID = "android.intent.extra.PID";
- static final String EXTRA_MAX = "android.intent.extra.MAX";
static final String EXTRA_NAME = "android.intent.extra.NAME";
static final String EXTRA_TITLE = "android.intent.extra.TITLE";
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
@@ -178,7 +174,6 @@ public class BugreportProgressService extends Service {
// Maximum progress displayed in %.
private static final int CAPPED_PROGRESS = 99;
- private static final int CAPPED_MAX = 100;
/** Show the progress log every this percent. */
private static final int LOG_PROGRESS_STEP = 10;
@@ -190,14 +185,9 @@ public class BugreportProgressService extends Service {
*/
static final int SCREENSHOT_DELAY_SECONDS = 3;
- // TODO: will be gone once fully migrated to Binder
- /** System properties used to communicate with dumpstate progress. */
- private static final String DUMPSTATE_PREFIX = "dumpstate.";
- private static final String NAME_SUFFIX = ".name";
+ /** System property where dumpstate stores last triggered bugreport id */
+ private static final String PROPERTY_LAST_ID = "dumpstate.last_id";
- /** System property (and value) used to stop dumpstate. */
- // TODO: should call ActiveManager API instead
- private static final String CTL_STOP = "ctl.stop";
private static final String BUGREPORT_SERVICE = "bugreport";
/**
@@ -205,14 +195,33 @@ public class BugreportProgressService extends Service {
* <p>
* Must be a path supported by its FileProvider.
*/
- private static final String SCREENSHOT_DIR = "bugreports";
+ private static final String BUGREPORT_DIR = "bugreports";
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 dumpstate processes (keyed by id) */
- private final SparseArray<DumpstateListener> mProcesses = new SparseArray<>();
+ /** Managed bugreport info (keyed by id) */
+ @GuardedBy("mLock")
+ private final SparseArray<BugreportInfo> mBugreportInfos = new SparseArray<>();
private Context mContext;
@@ -222,7 +231,9 @@ public class BugreportProgressService extends Service {
private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog();
- private File mScreenshotsDir;
+ private File mBugreportsDir;
+
+ private BugreportManager mBugreportManager;
/**
* id of the notification used to set service on foreground.
@@ -243,20 +254,20 @@ public class BugreportProgressService extends Service {
private boolean mIsWatch;
private boolean mIsTv;
- private int mLastProgressPercent;
-
@Override
public void onCreate() {
mContext = getApplicationContext();
mMainThreadHandler = new Handler(Looper.getMainLooper());
mServiceHandler = new ServiceHandler("BugreportProgressServiceMainThread");
mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
-
- mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR);
- if (!mScreenshotsDir.exists()) {
- Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots");
- if (!mScreenshotsDir.mkdir()) {
- Log.w(TAG, "Could not create directory " + mScreenshotsDir);
+ startSelfIntent = new Intent(this, this.getClass());
+
+ mBugreportsDir = new File(getFilesDir(), BUGREPORT_DIR);
+ if (!mBugreportsDir.exists()) {
+ Log.i(TAG, "Creating directory " + mBugreportsDir
+ + " to store bugreports and screenshots");
+ if (!mBugreportsDir.mkdir()) {
+ Log.w(TAG, "Could not create directory " + mBugreportsDir);
}
}
final Configuration conf = mContext.getResources().getConfiguration();
@@ -277,6 +288,9 @@ public class BugreportProgressService extends Service {
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;
@@ -303,21 +317,171 @@ public class BugreportProgressService extends Service {
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- final int size = mProcesses.size();
- if (size == 0) {
- writer.println("No monitored processes");
+ synchronized (mLock) {
+ final int size = mBugreportInfos.size();
+ if (size == 0) {
+ writer.println("No monitored processes");
+ return;
+ }
+ writer.print("Foreground id: "); writer.println(mForegroundId);
+ writer.println("\n");
+ writer.println("Monitored dumpstate processes");
+ writer.println("-----------------------------");
+ for (int i = 0; i < size; i++) {
+ writer.print("#");
+ writer.println(i + 1);
+ writer.println(getInfoLocked(mBugreportInfos.keyAt(i)));
+ }
+ }
+ }
+
+ private static String getFileName(BugreportInfo info, String suffix) {
+ return String.format("%s-%s%s", info.baseName, info.getName(), suffix);
+ }
+
+ private final class BugreportCallbackImpl extends BugreportCallback {
+
+ @GuardedBy("mLock")
+ private final BugreportInfo mInfo;
+
+ BugreportCallbackImpl(BugreportInfo info) {
+ mInfo = info;
+ }
+
+ @Override
+ public void onProgress(float progress) {
+ synchronized (mLock) {
+ checkProgressUpdatedLocked(mInfo, (int) progress);
+ }
+ }
+
+ /**
+ * Logs errors and stops the service on which this bugreport was running.
+ * Also stops progress notification (if any).
+ */
+ @Override
+ public void onError(@BugreportErrorCode int errorCode) {
+ synchronized (mLock) {
+ stopProgressLocked(mInfo.id);
+ mInfo.deleteEmptyFiles();
+ }
+ Log.e(TAG, "Bugreport API callback onError() errorCode = " + errorCode);
+ return;
+ }
+
+ @Override
+ public void onFinished() {
+ mInfo.renameBugreportFile();
+ mInfo.renameScreenshots();
+ synchronized (mLock) {
+ sendBugreportFinishedBroadcastLocked();
+ }
+ }
+
+ /**
+ * Reads bugreport id and links it to the bugreport info to track a bugreport that is in
+ * process. id is incremented in the dumpstate code.
+ * We do not track a bugreport if there is already a bugreport with the same id being
+ * tracked.
+ */
+ @GuardedBy("mLock")
+ private void trackInfoWithIdLocked() {
+ final int id = SystemProperties.getInt(PROPERTY_LAST_ID, 1);
+ if (mBugreportInfos.get(id) == null) {
+ mInfo.id = id;
+ mBugreportInfos.put(mInfo.id, mInfo);
+ }
return;
}
- writer.print("Foreground id: "); writer.println(mForegroundId);
- writer.println("\n");
- writer.println("Monitored dumpstate processes");
- writer.println("-----------------------------");
- for (int i = 0; i < size; i++) {
- writer.print("#"); writer.println(i + 1);
- writer.println(mProcesses.valueAt(i).info);
+
+ @GuardedBy("mLock")
+ private void sendBugreportFinishedBroadcastLocked() {
+ final String bugreportFilePath = mInfo.bugreportFile.getAbsolutePath();
+ if (mInfo.bugreportFile.length() == 0) {
+ Log.e(TAG, "Bugreport file empty. File path = " + bugreportFilePath);
+ return;
+ }
+ if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE) {
+ sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath,
+ mInfo.bugreportFile);
+ } else {
+ cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
+ final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
+ intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
+ intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo));
+ mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
+ onBugreportFinished(mInfo);
+ }
}
}
+ private static void sendRemoteBugreportFinishedBroadcast(Context context,
+ String bugreportFileName, File bugreportFile) {
+ cleanupOldFiles(REMOTE_BUGREPORT_FILES_AMOUNT, REMOTE_MIN_KEEP_AGE,
+ bugreportFile.getParentFile());
+ final Intent intent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH);
+ final Uri bugreportUri = getUri(context, bugreportFile);
+ final String bugreportHash = generateFileHash(bugreportFileName);
+ if (bugreportHash == null) {
+ Log.e(TAG, "Error generating file hash for remote bugreport");
+ }
+ intent.setDataAndType(bugreportUri, BUGREPORT_MIMETYPE);
+ intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
+ intent.putExtra(EXTRA_BUGREPORT, bugreportFileName);
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM,
+ android.Manifest.permission.DUMP);
+ }
+
+ /**
+ * Checks if screenshot array is non-empty and returns the first screenshot's path. The first
+ * screenshot is the default screenshot for the bugreport types that take it.
+ */
+ private static String getScreenshotForIntent(BugreportInfo info) {
+ if (!info.screenshotFiles.isEmpty()) {
+ final File screenshotFile = info.screenshotFiles.get(0);
+ final String screenshotFilePath = screenshotFile.getAbsolutePath();
+ return screenshotFilePath;
+ }
+ return null;
+ }
+
+ private static 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, File bugreportsDir) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ FileUtils.deleteOlderFiles(bugreportsDir, minCount, minAge);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "RuntimeException deleting old files", e);
+ }
+ return null;
+ }
+ }.execute();
+ }
+
/**
* Main thread used to handle all requests but taking screenshots.
*/
@@ -354,34 +518,21 @@ public class BugreportProgressService extends Service {
Log.v(TAG, "handleMessage(): " + dumpIntent((Intent) parcel));
final Intent intent;
if (parcel instanceof Intent) {
- // The real intent was passed to BugreportReceiver, which delegated to the service.
+ // The real intent was passed to BugreportRequestedReceiver,
+ // which delegated to the service.
intent = (Intent) parcel;
} else {
intent = (Intent) msg.obj;
}
final String action = intent.getAction();
- final int pid = intent.getIntExtra(EXTRA_PID, 0);
final int id = intent.getIntExtra(EXTRA_ID, 0);
- final int max = intent.getIntExtra(EXTRA_MAX, -1);
final String name = intent.getStringExtra(EXTRA_NAME);
if (DEBUG)
- Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id + ", pid: "
- + pid + ", max: " + max);
+ Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id);
switch (action) {
- case INTENT_BUGREPORT_STARTED:
- if (!startProgress(name, id, pid, max)) {
- stopSelfWhenDone();
- return;
- }
- break;
- case INTENT_BUGREPORT_FINISHED:
- if (id == 0) {
- // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
- // out-of-sync dumpstate process.
- Log.w(TAG, "Missing " + EXTRA_ID + " on intent " + intent);
- }
- onBugreportFinished(id, intent);
+ case INTENT_BUGREPORT_REQUESTED:
+ startBugreportAPI(intent);
break;
case INTENT_BUGREPORT_INFO_LAUNCH:
launchBugreportInfoDialog(id);
@@ -421,52 +572,113 @@ public class BugreportProgressService extends Service {
}
}
- private BugreportInfo getInfo(int id) {
- final DumpstateListener listener = mProcesses.get(id);
- if (listener == null) {
- Log.w(TAG, "Not monitoring process with ID " + id);
+ @GuardedBy("mLock")
+ private BugreportInfo getInfoLocked(int id) {
+ final BugreportInfo bugreportInfo = mBugreportInfos.get(id);
+ if (bugreportInfo == null) {
+ Log.w(TAG, "Not monitoring bugreports with ID " + id);
return null;
}
- return listener.info;
+ return bugreportInfo;
}
- /**
- * Creates the {@link BugreportInfo} for a process and issue a system notification to
- * indicate its progress.
- *
- * @return whether it succeeded or not.
- */
- private boolean startProgress(String name, int id, int pid, int max) {
- if (name == null) {
- Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
+ private String getBugreportBaseName(@BugreportParams.BugreportMode int type) {
+ String buildId = SystemProperties.get("ro.build.id", "UNKNOWN_BUILD");
+ String deviceName = SystemProperties.get("ro.product.name", "UNKNOWN_DEVICE");
+ String typeSuffix = null;
+ if (type == BugreportParams.BUGREPORT_MODE_WIFI) {
+ typeSuffix = "wifi";
+ } else if (type == BugreportParams.BUGREPORT_MODE_TELEPHONY) {
+ typeSuffix = "telephony";
+ } else {
+ return String.format("bugreport-%s-%s", deviceName, buildId);
}
- if (id == -1) {
- Log.e(TAG, "Missing " + EXTRA_ID + " on start intent");
- return false;
+ return String.format("bugreport-%s-%s-%s", deviceName, buildId, typeSuffix);
+ }
+
+ private void startBugreportAPI(Intent intent) {
+ String shareTitle = intent.getStringExtra(EXTRA_TITLE);
+ String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION);
+ int bugreportType = intent.getIntExtra(EXTRA_BUGREPORT_TYPE,
+ BugreportParams.BUGREPORT_MODE_INTERACTIVE);
+ String baseName = getBugreportBaseName(bugreportType);
+ String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
+
+ BugreportInfo info = new BugreportInfo(mContext, baseName, name,
+ shareTitle, shareDescription, bugreportType, mBugreportsDir);
+ ParcelFileDescriptor bugreportFd = info.getBugreportFd();
+ if (bugreportFd == null) {
+ Log.e(TAG, "Failed to start bugreport generation as "
+ + " bugreport parcel file descriptor is null.");
+ return;
}
- if (pid == -1) {
- Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
- return false;
+ ParcelFileDescriptor screenshotFd = null;
+ if (isDefaultScreenshotRequired(bugreportType, /* hasScreenshotButton= */ !mIsTv)) {
+ screenshotFd = info.getDefaultScreenshotFd();
+ if (screenshotFd == null) {
+ Log.e(TAG, "Failed to start bugreport generation as"
+ + " screenshot parcel file descriptor is null. Deleting bugreport file");
+ FileUtils.closeQuietly(bugreportFd);
+ info.bugreportFile.delete();
+ return;
+ }
}
- if (max <= 0) {
- Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
- return false;
+
+ mBugreportManager = (BugreportManager) mContext.getSystemService(
+ Context.BUGREPORT_SERVICE);
+ final Executor executor = ActivityThread.currentActivityThread().getExecutor();
+
+ Log.i(TAG, "bugreport type = " + bugreportType
+ + " bugreport file fd: " + bugreportFd
+ + " screenshot file fd: " + screenshotFd);
+
+ BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(info);
+ try {
+ synchronized (mLock) {
+ mBugreportManager.startBugreport(bugreportFd, screenshotFd,
+ new BugreportParams(bugreportType), executor, bugreportCallback);
+ bugreportCallback.trackInfoWithIdLocked();
+ }
+ } catch (RuntimeException e) {
+ Log.i(TAG, "Error in generating bugreports: ", e);
+ // The binder call didn't go through successfully, so need to close the fds.
+ // If the calls went through API takes ownership.
+ FileUtils.closeQuietly(bugreportFd);
+ if (screenshotFd != null) {
+ FileUtils.closeQuietly(screenshotFd);
+ }
}
+ }
+
+ private static boolean isDefaultScreenshotRequired(
+ @BugreportParams.BugreportMode int bugreportType,
+ boolean hasScreenshotButton) {
+ // Modify dumpstate#SetOptionsFromMode as well for default system screenshots.
+ // We override dumpstate for interactive bugreports with a screenshot button.
+ return (bugreportType == BugreportParams.BUGREPORT_MODE_INTERACTIVE && !hasScreenshotButton)
+ || bugreportType == BugreportParams.BUGREPORT_MODE_FULL
+ || bugreportType == BugreportParams.BUGREPORT_MODE_WEAR;
+ }
- final BugreportInfo info = new BugreportInfo(mContext, id, pid, name, max);
- if (mProcesses.indexOfKey(id) >= 0) {
- // BUGREPORT_STARTED intent was already received; ignore it.
- Log.w(TAG, "ID " + id + " already watched");
- return true;
+ private static ParcelFileDescriptor getFd(File file) {
+ try {
+ return ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "Error in generating bugreports: ", e);
}
- final DumpstateListener listener = new DumpstateListener(info);
- mProcesses.put(info.id, listener);
- if (listener.connect()) {
- updateProgress(info);
- return true;
- } else {
- Log.w(TAG, "not updating progress because it could not connect to dumpstate");
- return false;
+ return null;
+ }
+
+ private static void createReadWriteFile(File file) {
+ try {
+ if (!file.exists()) {
+ file.createNewFile();
+ file.setReadable(true, true);
+ file.setWritable(true, true);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error in creating bugreport file: ", e);
}
}
@@ -474,12 +686,12 @@ public class BugreportProgressService extends Service {
* Updates the system notification for a given bugreport.
*/
private void updateProgress(BugreportInfo info) {
- if (info.max <= 0 || info.progress < 0) {
+ if (info.progress.intValue() < 0) {
Log.e(TAG, "Invalid progress values for " + info);
return;
}
- if (info.finished) {
+ if (info.finished.get()) {
Log.w(TAG, "Not sending progress notification because bugreport has finished already ("
+ info + ")");
return;
@@ -488,7 +700,7 @@ public class BugreportProgressService extends Service {
final NumberFormat nf = NumberFormat.getPercentInstance();
nf.setMinimumFractionDigits(2);
nf.setMaximumFractionDigits(2);
- final String percentageText = nf.format((double) info.progress / info.max);
+ final String percentageText = nf.format((double) info.progress.intValue() / 100);
String title = mContext.getString(R.string.bugreport_in_progress_title, info.id);
@@ -496,18 +708,20 @@ public class BugreportProgressService extends Service {
if (mIsWatch) {
nf.setMinimumFractionDigits(0);
nf.setMaximumFractionDigits(0);
- final String watchPercentageText = nf.format((double) info.progress / info.max);
+ final String watchPercentageText = nf.format((double) info.progress.intValue() / 100);
title = title + "\n" + watchPercentageText;
}
final String name =
- info.name != null ? info.name : mContext.getString(R.string.bugreport_unnamed);
+ info.getName() != null ? info.getName()
+ : mContext.getString(R.string.bugreport_unnamed);
final Notification.Builder builder = newBaseNotification(mContext)
.setContentTitle(title)
.setTicker(title)
.setContentText(name)
- .setProgress(info.max, info.progress, false)
+ .setProgress(100 /* max value of progress percentage */,
+ info.progress.intValue(), false)
.setOngoing(true);
// Wear and ATV bugreport doesn't need the bug info dialog, screenshot and cancel action.
@@ -536,13 +750,14 @@ public class BugreportProgressService extends Service {
.setActions(infoAction, screenshotAction, cancelAction);
}
// Show a debug log, every LOG_PROGRESS_STEP percent.
- final int progress = (info.progress * 100) / info.max;
+ final int progress = info.progress.intValue();
- if ((info.progress == 0) || (info.progress >= 100) ||
- ((progress / LOG_PROGRESS_STEP) != (mLastProgressPercent / LOG_PROGRESS_STEP))) {
+ if ((progress == 0) || (progress >= 100)
+ || ((progress / LOG_PROGRESS_STEP)
+ != (info.lastProgress.intValue() / LOG_PROGRESS_STEP))) {
Log.d(TAG, "Progress #" + info.id + ": " + percentageText);
}
- mLastProgressPercent = progress;
+ info.lastProgress.set(progress);
sendForegroundabledNotification(info.id, builder.build());
}
@@ -554,6 +769,9 @@ public class BugreportProgressService extends Service {
} 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);
}
}
@@ -572,18 +790,19 @@ public class BugreportProgressService extends Service {
/**
* Finalizes the progress on a given bugreport and cancel its notification.
*/
- private void stopProgress(int id) {
- if (mProcesses.indexOfKey(id) < 0) {
+ @GuardedBy("mLock")
+ private void stopProgressLocked(int id) {
+ if (mBugreportInfos.indexOfKey(id) < 0) {
Log.w(TAG, "ID not watched: " + id);
} else {
Log.d(TAG, "Removing ID " + id);
- mProcesses.remove(id);
+ mBugreportInfos.remove(id);
}
// Must stop foreground service first, otherwise notif.cancel() will fail below.
- stopForegroundWhenDone(id);
+ stopForegroundWhenDoneLocked(id);
Log.d(TAG, "stopProgress(" + id + "): cancel notification");
NotificationManager.from(mContext).cancel(id);
- stopSelfWhenDone();
+ stopSelfWhenDoneLocked();
}
/**
@@ -593,13 +812,16 @@ public class BugreportProgressService extends Service {
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_CANCEL);
Log.v(TAG, "cancel: ID=" + id);
mInfoDialog.cancel();
- final BugreportInfo info = getInfo(id);
- if (info != null && !info.finished) {
- Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
- setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
- deleteScreenshots(info);
+ synchronized (mLock) {
+ final BugreportInfo info = getInfoLocked(id);
+ if (info != null && !info.finished.get()) {
+ Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
+ mBugreportManager.cancelBugreport();
+ info.deleteScreenshots();
+ info.deleteBugreportFile();
+ }
+ stopProgressLocked(id);
}
- stopProgress(id);
}
/**
@@ -608,7 +830,10 @@ public class BugreportProgressService extends Service {
*/
private void launchBugreportInfoDialog(int id) {
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_DETAILS);
- final BugreportInfo info = getInfo(id);
+ final BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
if (info == null) {
// Most likely am killed Shell before user tapped the notification. Since system might
// be too busy anwyays, it's better to ignore the notification and switch back to the
@@ -642,7 +867,11 @@ public class BugreportProgressService extends Service {
*/
private void takeScreenshot(int id) {
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
- if (getInfo(id) == null) {
+ BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
+ if (info == null) {
// Most likely am killed Shell before user tapped the notification. Since system might
// be too busy anwyays, it's better to ignore the notification and switch back to the
// non-interactive mode (where the bugerport will be shared upon completion).
@@ -677,14 +906,16 @@ public class BugreportProgressService extends Service {
mServiceHandler.sendMessageDelayed(msg, DateUtils.SECOND_IN_MILLIS);
return;
}
-
+ final BugreportInfo info;
// It's time to take the screenshot: let the proper thread handle it
- final BugreportInfo info = getInfo(id);
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
if (info == null) {
return;
}
final String screenshotPath =
- new File(mScreenshotsDir, info.getPathNextScreenshot()).getAbsolutePath();
+ new File(mBugreportsDir, info.getPathNextScreenshot()).getAbsolutePath();
Message.obtain(mScreenshotHandler, MSG_SCREENSHOT_REQUEST, id, UNUSED_ARG2, screenshotPath)
.sendToTarget();
@@ -695,11 +926,11 @@ public class BugreportProgressService extends Service {
* SCREENSHOT button is enabled or disabled accordingly.
*/
private void setTakingScreenshot(boolean flag) {
- synchronized (BugreportProgressService.this) {
+ synchronized (mLock) {
mTakingScreenshot = flag;
- for (int i = 0; i < mProcesses.size(); i++) {
- final BugreportInfo info = mProcesses.valueAt(i).info;
- if (info.finished) {
+ for (int i = 0; i < mBugreportInfos.size(); i++) {
+ final BugreportInfo info = getInfoLocked(mBugreportInfos.keyAt(i));
+ if (info.finished.get()) {
Log.d(TAG, "Not updating progress for " + info.id + " while taking screenshot"
+ " because share notification was already sent");
continue;
@@ -720,7 +951,10 @@ public class BugreportProgressService extends Service {
private void handleScreenshotResponse(Message resultMsg) {
final boolean taken = resultMsg.arg2 != 0;
- final BugreportInfo info = getInfo(resultMsg.arg1);
+ final BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(resultMsg.arg1);
+ }
if (info == null) {
return;
}
@@ -729,9 +963,9 @@ public class BugreportProgressService extends Service {
final String msg;
if (taken) {
info.addScreenshot(screenshotFile);
- if (info.finished) {
+ if (info.finished.get()) {
Log.d(TAG, "Screenshot finished after bugreport; updating share notification");
- info.renameScreenshots(mScreenshotsDir);
+ info.renameScreenshots();
sendBugreportNotification(info, mTakingScreenshot);
}
msg = mContext.getString(R.string.bugreport_screenshot_taken);
@@ -743,21 +977,12 @@ public class BugreportProgressService extends Service {
}
/**
- * Deletes all screenshots taken for a given bugreport.
- */
- private void deleteScreenshots(BugreportInfo info) {
- for (File file : info.screenshotFiles) {
- Log.i(TAG, "Deleting screenshot file " + file);
- file.delete();
- }
- }
-
- /**
* Stop running on foreground once there is no more active bugreports being watched.
*/
- private void stopForegroundWhenDone(int id) {
+ @GuardedBy("mLock")
+ private void stopForegroundWhenDoneLocked(int id) {
if (id != mForegroundId) {
- Log.d(TAG, "stopForegroundWhenDone(" + id + "): ignoring since foreground id is "
+ Log.d(TAG, "stopForegroundWhenDoneLocked(" + id + "): ignoring since foreground id is "
+ mForegroundId);
return;
}
@@ -767,11 +992,11 @@ public class BugreportProgressService extends Service {
mForegroundId = -1;
// Might need to restart foreground using a new notification id.
- final int total = mProcesses.size();
+ final int total = mBugreportInfos.size();
if (total > 0) {
for (int i = 0; i < total; i++) {
- final BugreportInfo info = mProcesses.valueAt(i).info;
- if (!info.finished) {
+ final BugreportInfo info = getInfoLocked(mBugreportInfos.keyAt(i));
+ if (!info.finished.get()) {
updateProgress(info);
break;
}
@@ -782,9 +1007,10 @@ public class BugreportProgressService extends Service {
/**
* Finishes the service when it's not monitoring any more processes.
*/
- private void stopSelfWhenDone() {
- if (mProcesses.size() > 0) {
- if (DEBUG) Log.d(TAG, "Staying alive, waiting for IDs " + mProcesses);
+ @GuardedBy("mLock")
+ private void stopSelfWhenDoneLocked() {
+ if (mBugreportInfos.size() > 0) {
+ if (DEBUG) Log.d(TAG, "Staying alive, waiting for IDs " + mBugreportInfos);
return;
}
Log.v(TAG, "No more processes to handle, shutting down");
@@ -792,58 +1018,20 @@ public class BugreportProgressService extends Service {
}
/**
- * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
- */
- private void onBugreportFinished(int id, Intent intent) {
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- if (bugreportFile == null) {
- // Should never happen, dumpstate always set the file.
- Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent);
- return;
- }
- final int max = intent.getIntExtra(EXTRA_MAX, -1);
- final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
- final String shareTitle = intent.getStringExtra(EXTRA_TITLE);
- final String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION);
- onBugreportFinished(id, bugreportFile, screenshotFile, shareTitle, shareDescription, max);
- }
-
- /**
* Wraps up bugreport generation and triggers a notification to share the bugreport.
*/
- private void onBugreportFinished(int id, File bugreportFile, @Nullable File screenshotFile,
- String shareTitle, String shareDescription, int max) {
- mInfoDialog.onBugreportFinished();
- BugreportInfo info = getInfo(id);
- if (info == null) {
- // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED first.
- Log.v(TAG, "Creating info for untracked ID " + id);
- info = new BugreportInfo(mContext, id);
- mProcesses.put(id, new DumpstateListener(info));
- }
- info.renameScreenshots(mScreenshotsDir);
- info.bugreportFile = bugreportFile;
- if (screenshotFile != null) {
- info.addScreenshot(screenshotFile);
+ private void onBugreportFinished(BugreportInfo info) {
+ if (!TextUtils.isEmpty(info.shareTitle)) {
+ info.setTitle(info.shareTitle);
}
+ Log.d(TAG, "Bugreport finished with title: " + info.getTitle()
+ + " and shareDescription: " + info.shareDescription);
+ info.finished.set(true);
- if (max != -1) {
- MetricsLogger.histogram(this, "dumpstate_duration", max);
- info.max = max;
- }
-
- if (!TextUtils.isEmpty(shareTitle)) {
- info.title = shareTitle;
- if (!TextUtils.isEmpty(shareDescription)) {
- info.shareDescription= shareDescription;
- }
- Log.d(TAG, "Bugreport title is " + info.title + ","
- + " shareDescription is " + info.shareDescription);
+ synchronized (mLock) {
+ // Stop running on foreground, otherwise share notification cannot be dismissed.
+ stopForegroundWhenDoneLocked(info.id);
}
- info.finished = true;
-
- // Stop running on foreground, otherwise share notification cannot be dismissed.
- stopForegroundWhenDone(id);
triggerLocalNotification(mContext, info);
}
@@ -858,7 +1046,9 @@ public class BugreportProgressService extends Service {
if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
Toast.makeText(context, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
- stopProgress(info.id);
+ synchronized (mLock) {
+ stopProgressLocked(info.id);
+ }
return;
}
@@ -882,6 +1072,9 @@ public class BugreportProgressService extends Service {
* Build {@link Intent} that can be used to share the given bugreport.
*/
private static Intent buildSendIntent(Context context, BugreportInfo info) {
+ // Rename files (if required) before sharing
+ info.renameBugreportFile();
+ info.renameScreenshots();
// Files are kept on private storage, so turn into Uris that we can
// grant temporary permissions for.
final Uri bugreportUri;
@@ -900,8 +1093,8 @@ public class BugreportProgressService extends Service {
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setType(mimeType);
- final String subject = !TextUtils.isEmpty(info.title) ?
- info.title : bugreportUri.getLastPathSegment();
+ final String subject = !TextUtils.isEmpty(info.getTitle())
+ ? info.getTitle() : bugreportUri.getLastPathSegment();
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
// EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
@@ -912,9 +1105,9 @@ public class BugreportProgressService extends Service {
.append("\nSerial number: ")
.append(SystemProperties.get("ro.serialno"));
int descriptionLength = 0;
- if (!TextUtils.isEmpty(info.description)) {
- messageBody.append("\nDescription: ").append(info.description);
- descriptionLength = info.description.length();
+ if (!TextUtils.isEmpty(info.getDescription())) {
+ messageBody.append("\nDescription: ").append(info.getDescription());
+ descriptionLength = info.getDescription().length();
}
intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
final ClipData clipData = new ClipData(null, new String[] { mimeType },
@@ -955,12 +1148,17 @@ public class BugreportProgressService extends Service {
*/
private void shareBugreport(int id, BugreportInfo sharedInfo) {
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SHARE);
- BugreportInfo info = getInfo(id);
+ BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
if (info == null) {
// Service was terminated but notification persisted
info = sharedInfo;
- Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
- + mProcesses + "), using info from intent instead (" + info + ")");
+ synchronized (mLock) {
+ Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
+ + mBugreportInfos + "), using info from intent instead (" + info + ")");
+ }
} else {
Log.v(TAG, "shareBugReport(): id " + id + " info = " + info);
}
@@ -970,7 +1168,9 @@ public class BugreportProgressService extends Service {
final Intent sendIntent = buildSendIntent(mContext, info);
if (sendIntent == null) {
Log.w(TAG, "Stopping progres on ID " + id + " because share intent could not be built");
- stopProgress(id);
+ synchronized (mLock) {
+ stopProgressLocked(id);
+ }
return;
}
@@ -993,9 +1193,10 @@ public class BugreportProgressService extends Service {
} else {
mContext.startActivity(notifIntent);
}
-
- // ... and stop watching this process.
- stopProgress(id);
+ synchronized (mLock) {
+ // ... and stop watching this process.
+ stopProgressLocked(id);
+ }
}
static void sendShareIntent(Context context, Intent intent) {
@@ -1029,10 +1230,10 @@ public class BugreportProgressService extends Service {
mContext.getString(R.string.bugreport_finished_pending_screenshot_text)
: mContext.getString(R.string.bugreport_finished_text);
final String title;
- if (TextUtils.isEmpty(info.title)) {
+ if (TextUtils.isEmpty(info.getTitle())) {
title = mContext.getString(R.string.bugreport_finished_title, info.id);
} else {
- title = info.title;
+ title = info.getTitle();
if (!TextUtils.isEmpty(info.shareDescription)) {
if(!takingScreenshot) content = info.shareDescription;
}
@@ -1046,8 +1247,8 @@ public class BugreportProgressService extends Service {
PendingIntent.FLAG_UPDATE_CURRENT))
.setDeleteIntent(newCancelIntent(mContext, info));
- if (!TextUtils.isEmpty(info.name)) {
- builder.setSubText(info.name);
+ if (!TextUtils.isEmpty(info.getName())) {
+ builder.setSubText(info.getName());
}
Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
@@ -1140,13 +1341,14 @@ public class BugreportProgressService extends Service {
}
}
+ @GuardedBy("mLock")
private void addDetailsToZipFileLocked(BugreportInfo info) {
if (info.bugreportFile == null) {
// One possible reason is a bug in the Parcelization code.
Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
return;
}
- if (TextUtils.isEmpty(info.title) && TextUtils.isEmpty(info.description)) {
+ if (TextUtils.isEmpty(info.getTitle()) && TextUtils.isEmpty(info.getDescription())) {
Log.d(TAG, "Not touching zip file since neither title nor description are set");
return;
}
@@ -1179,8 +1381,8 @@ public class BugreportProgressService extends Service {
}
// Then add the user-provided info.
- addEntry(zos, "title.txt", info.title);
- addEntry(zos, "description.txt", info.description);
+ addEntry(zos, "title.txt", info.getTitle());
+ addEntry(zos, "description.txt", info.getDescription());
} catch (IOException e) {
Log.e(TAG, "exception zipping file " + tmpZip, e);
Toast.makeText(mContext, R.string.bugreport_add_details_to_zip_failed,
@@ -1190,7 +1392,7 @@ public class BugreportProgressService extends Service {
// Make sure it only tries to add details once, even it fails the first time.
info.addedDetailsToZip = true;
info.addingDetailsToZip = false;
- stopForegroundWhenDone(info.id);
+ stopForegroundWhenDoneLocked(info.id);
}
if (!tmpZip.renameTo(info.bugreportFile)) {
@@ -1292,13 +1494,11 @@ public class BugreportProgressService extends Service {
}
String action = intent.getAction();
if (action == null) {
- // Happens when BugreportReceiver calls startService...
+ // Happens when startService is called...
action = "no action";
}
final StringBuilder buffer = new StringBuilder(action).append(" extras: ");
addExtra(buffer, intent, EXTRA_ID);
- addExtra(buffer, intent, EXTRA_PID);
- addExtra(buffer, intent, EXTRA_MAX);
addExtra(buffer, intent, EXTRA_NAME);
addExtra(buffer, intent, EXTRA_DESCRIPTION);
addExtra(buffer, intent, EXTRA_BUGREPORT);
@@ -1342,36 +1542,30 @@ public class BugreportProgressService extends Service {
}
/**
- * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
- */
- private boolean setBugreportNameProperty(int pid, String name) {
- Log.d(TAG, "Updating bugreport name to " + name);
- final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
- return setSystemProperty(key, name);
- }
-
- /**
* Updates the user-provided details of a bugreport.
*/
private void updateBugreportInfo(int id, String name, String title, String description) {
- final BugreportInfo info = getInfo(id);
+ final BugreportInfo info;
+ synchronized (mLock) {
+ info = getInfoLocked(id);
+ }
if (info == null) {
return;
}
- if (title != null && !title.equals(info.title)) {
+ if (title != null && !title.equals(info.getTitle())) {
Log.d(TAG, "updating bugreport title: " + title);
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_TITLE_CHANGED);
}
- info.title = title;
- if (description != null && !description.equals(info.description)) {
+ info.setTitle(title);
+ if (description != null && !description.equals(info.getDescription())) {
Log.d(TAG, "updating bugreport description: " + description.length() + " chars");
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_DESCRIPTION_CHANGED);
}
- info.description = description;
- if (name != null && !name.equals(info.name)) {
+ info.setDescription(description);
+ if (name != null && !name.equals(info.getName())) {
Log.d(TAG, "updating bugreport name: " + name);
MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_NAME_CHANGED);
- info.name = name;
+ info.setName(name);
updateProgress(info);
}
}
@@ -1434,30 +1628,6 @@ public class BugreportProgressService extends Service {
private AlertDialog mDialog;
private Button mOkButton;
private int mId;
- private int mPid;
-
- /**
- * Last "committed" value of the bugreport name.
- * <p>
- * Once initially set, it's only updated when user clicks the OK button.
- */
- private String mSavedName;
-
- /**
- * Last value of the bugreport name as entered by the user.
- * <p>
- * Every time it's changed the equivalent system property is changed as well, but if the
- * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
- * <p>
- * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
- * user changed the name but didn't clicked OK yet (for example, because the user is typing
- * the description). The only drawback is that if the user changes the name while
- * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
- * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
- * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
- * such drawback.
- */
- private String mTempName;
/**
* Sets its internal state and displays the dialog.
@@ -1477,18 +1647,6 @@ public class BugreportProgressService extends Service {
mInfoName = (EditText) view.findViewById(R.id.name);
mInfoTitle = (EditText) view.findViewById(R.id.title);
mInfoDescription = (EditText) view.findViewById(R.id.description);
-
- mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- return;
- }
- sanitizeName();
- }
- });
-
mDialog = new AlertDialog.Builder(themedContext)
.setView(view)
.setTitle(dialogTitle)
@@ -1503,11 +1661,6 @@ public class BugreportProgressService extends Service {
{
MetricsLogger.action(context,
MetricsEvent.ACTION_BUGREPORT_DETAILS_CANCELED);
- if (!mTempName.equals(mSavedName)) {
- // Must restore dumpstate's name since it was changed
- // before user clicked OK.
- setBugreportNameProperty(mPid, mSavedName);
- }
}
})
.create();
@@ -1526,17 +1679,15 @@ public class BugreportProgressService extends Service {
}
// Then set fields.
- mSavedName = mTempName = info.name;
mId = info.id;
- mPid = info.pid;
- if (!TextUtils.isEmpty(info.name)) {
- mInfoName.setText(info.name);
+ if (!TextUtils.isEmpty(info.getName())) {
+ mInfoName.setText(info.getName());
}
- if (!TextUtils.isEmpty(info.title)) {
- mInfoTitle.setText(info.title);
+ if (!TextUtils.isEmpty(info.getTitle())) {
+ mInfoTitle.setText(info.getTitle());
}
- if (!TextUtils.isEmpty(info.description)) {
- mInfoDescription.setText(info.description);
+ if (!TextUtils.isEmpty(info.getDescription())) {
+ mInfoDescription.setText(info.getDescription());
}
// And finally display it.
@@ -1555,7 +1706,7 @@ public class BugreportProgressService extends Service {
@Override
public void onClick(View view) {
MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
- sanitizeName();
+ sanitizeName(info.getName());
final String name = mInfoName.getText().toString();
final String title = mInfoTitle.getText().toString();
final String description = mInfoDescription.getText().toString();
@@ -1571,9 +1722,9 @@ public class BugreportProgressService extends Service {
* Sanitizes the user-provided value for the {@code name} field, automatically replacing
* invalid characters if necessary.
*/
- private void sanitizeName() {
+ private void sanitizeName(String savedName) {
String name = mInfoName.getText().toString();
- if (name.equals(mTempName)) {
+ if (name.equals(savedName)) {
if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
return;
}
@@ -1593,25 +1744,6 @@ public class BugreportProgressService extends Service {
name = safeName.toString();
mInfoName.setText(name);
}
- mTempName = name;
-
- // Must update system property for the cases where dumpstate finishes
- // while the user is still entering other fields (like title or
- // description)
- setBugreportNameProperty(mPid, name);
- }
-
- /**
- * Notifies the dialog that the bugreport has finished so it disables the {@code name}
- * field.
- * <p>Once the bugreport is finished dumpstate has already generated the final files, so
- * changing the name would have no effect.
- */
- void onBugreportFinished() {
- if (mInfoName != null) {
- mInfoName.setEnabled(false);
- mInfoName.setText(mSavedName);
- }
}
void cancel() {
@@ -1630,57 +1762,66 @@ public class BugreportProgressService extends Service {
/**
* Sequential, user-friendly id used to identify the bugreport.
*/
- final int id;
+ int id;
/**
- * {@code pid} of the {@code dumpstate} process generating the bugreport.
+ * Prefix name of the bugreport, this is uneditable.
+ * The baseName consists of the string "bugreport" + deviceName + buildID
+ * This will end with the string "wifi"/"telephony" for wifi/telephony bugreports.
+ * Bugreport zip file name = "<baseName>-<name>.zip"
*/
- final int pid;
+ private final String baseName;
/**
- * Name of the bugreport, will be used to rename the final files.
- * <p>
- * Initial value is the bugreport filename reported by {@code dumpstate}, but user can
- * change it later to a more meaningful name.
+ * Suffix name of the bugreport/screenshot, is set to timestamp initially. User can make
+ * modifications to this using interface.
*/
- String name;
+ private String name;
/**
- * User-provided, one-line summary of the bug; when set, will be used as the subject
- * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
+ * Initial value of the field name. This is required to rename the files later on, as they
+ * are created using initial value of name.
*/
- String title;
+ private final String initialName;
/**
- * User-provided, detailed description of the bugreport; when set, will be added to the body
+ * User-provided, one-line summary of the bug; when set, will be used as the subject
* of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
*/
- String description;
+ private String title;
/**
- * Maximum progress of the bugreport generation as displayed by the UI.
+ * One-line summary of the bug; when set, will be used as the subject of the
+ * {@link Intent#ACTION_SEND_MULTIPLE} intent. This is the predefined title which is
+ * set initially when the request to take a bugreport is made. This overrides any changes
+ * in the title that the user makes after the bugreport starts.
*/
- int max;
+ private final String shareTitle;
/**
- * Current progress of the bugreport generation as displayed by the UI.
+ * User-provided, detailed description of the bugreport; when set, will be added to the body
+ * of the {@link Intent#ACTION_SEND_MULTIPLE} intent. This is shown in the app where the
+ * bugreport is being shared as an attachment. This is not related/dependant on
+ * {@code shareDescription}.
*/
- int progress;
+ private String description;
/**
- * Maximum progress of the bugreport generation as reported by dumpstate.
+ * Current value of progress (in percentage) of the bugreport generation as
+ * displayed by the UI.
*/
- int realMax;
+ final AtomicInteger progress = new AtomicInteger(0);
/**
- * Current progress of the bugreport generation as reported by dumpstate.
+ * Last value of progress (in percentage) of the bugreport generation for which
+ * system notification was updated.
*/
- int realProgress;
+ final AtomicInteger lastProgress = new AtomicInteger(0);
/**
* Time of the last progress update.
*/
- long lastUpdate = System.currentTimeMillis();
+ final AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis());
/**
* Time of the last progress update when Parcel was created.
@@ -1700,7 +1841,7 @@ public class BugreportProgressService extends Service {
/**
* Whether dumpstate sent an intent informing it has finished.
*/
- boolean finished;
+ final AtomicBoolean finished = new AtomicBoolean(false);
/**
* Whether the details entries have been added to the bugreport yet.
@@ -1714,36 +1855,106 @@ public class BugreportProgressService extends Service {
int screenshotCounter;
/**
- * Descriptive text that will be shown to the user in the notification message.
+ * Descriptive text that will be shown to the user in the notification message. This is the
+ * predefined description which is set initially when the request to take a bugreport is
+ * made.
*/
- String shareDescription;
+ private final String shareDescription;
/**
- * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
+ * Type of the bugreport
*/
- BugreportInfo(Context context, int id, int pid, String name, int max) {
- this.context = context;
- this.id = id;
- this.pid = pid;
- this.name = name;
- this.max = this.realMax = max;
- }
+ final int type;
+
+ private final Object mLock = new Object();
/**
- * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
- * without a previous call to BUGREPORT_STARTED.
+ * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
*/
- BugreportInfo(Context context, int id) {
- this(context, id, id, null, 0);
- this.finished = true;
+ BugreportInfo(Context context, String baseName, String name,
+ @Nullable String shareTitle, @Nullable String shareDescription,
+ @BugreportParams.BugreportMode int type, File bugreportsDir) {
+ this.context = context;
+ this.name = this.initialName = name;
+ this.shareTitle = shareTitle == null ? "" : shareTitle;
+ this.shareDescription = shareDescription == null ? "" : shareDescription;
+ this.type = type;
+ this.baseName = baseName;
+ createBugreportFile(bugreportsDir);
+ createScreenshotFile(bugreportsDir);
+ }
+
+ void createBugreportFile(File bugreportsDir) {
+ bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+ createReadWriteFile(bugreportFile);
+ }
+
+ void createScreenshotFile(File bugreportsDir) {
+ File screenshotFile = new File(bugreportsDir, getScreenshotName("default"));
+ addScreenshot(screenshotFile);
+ createReadWriteFile(screenshotFile);
+ }
+
+ ParcelFileDescriptor getBugreportFd() {
+ return getFd(bugreportFile);
+ }
+
+ ParcelFileDescriptor getDefaultScreenshotFd() {
+ if (screenshotFiles.isEmpty()) {
+ return null;
+ }
+ return getFd(screenshotFiles.get(0));
+ }
+
+ void setTitle(String title) {
+ synchronized (mLock) {
+ this.title = title;
+ }
+ }
+
+ String getTitle() {
+ synchronized (mLock) {
+ return title;
+ }
+ }
+
+ void setName(String name) {
+ synchronized (mLock) {
+ this.name = name;
+ }
+ }
+
+ String getName() {
+ synchronized (mLock) {
+ return name;
+ }
+ }
+
+ void setDescription(String description) {
+ synchronized (mLock) {
+ this.description = description;
+ }
+ }
+
+ String getDescription() {
+ synchronized (mLock) {
+ return description;
+ }
}
/**
- * Gets the name for next screenshot file.
+ * Gets the name for next user triggered screenshot file.
*/
String getPathNextScreenshot() {
screenshotCounter ++;
- return "screenshot-" + pid + "-" + screenshotCounter + ".png";
+ return getScreenshotName(Integer.toString(screenshotCounter));
+ }
+
+ /**
+ * Gets the name for screenshot file based on the suffix that is passed.
+ */
+ String getScreenshotName(String suffix) {
+ return "screenshot-" + initialName + "-" + suffix + ".png";
}
/**
@@ -1754,49 +1965,100 @@ public class BugreportProgressService extends Service {
}
/**
- * Rename all screenshots files so that they contain the user-generated name instead of pid.
+ * Deletes all screenshots taken for a given bugreport.
+ */
+ private void deleteScreenshots() {
+ for (File file : screenshotFiles) {
+ Log.i(TAG, "Deleting screenshot file " + file);
+ file.delete();
+ }
+ }
+
+ /**
+ * Deletes bugreport file for a given bugreport.
+ */
+ private void deleteBugreportFile() {
+ Log.i(TAG, "Deleting bugreport file " + bugreportFile);
+ bugreportFile.delete();
+ }
+
+ /**
+ * Deletes empty files for a given bugreport.
+ */
+ private void deleteEmptyFiles() {
+ if (bugreportFile.length() == 0) {
+ Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile);
+ bugreportFile.delete();
+ }
+ for (File file : screenshotFiles) {
+ if (file.length() == 0) {
+ Log.i(TAG, "Deleting empty screenshot file: " + file);
+ file.delete();
+ }
+ }
+ }
+
+ /**
+ * Rename all screenshots files so that they contain the new {@code name} instead of the
+ * {@code initialName} if user has changed it.
*/
- void renameScreenshots(File screenshotDir) {
+ void renameScreenshots() {
if (TextUtils.isEmpty(name)) {
return;
}
final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
for (File oldFile : screenshotFiles) {
final String oldName = oldFile.getName();
- final String newName = oldName.replaceFirst(Integer.toString(pid), name);
+ final String newName = oldName.replaceFirst(initialName, name);
final File newFile;
if (!newName.equals(oldName)) {
- final File renamedFile = new File(screenshotDir, newName);
+ final File renamedFile = new File(oldFile.getParentFile(), newName);
Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
} else {
- Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
+ Log.w(TAG, "Name didn't change: " + oldName);
newFile = oldFile;
}
- renamedFiles.add(newFile);
+ if (newFile.length() > 0) {
+ renamedFiles.add(newFile);
+ } else if (newFile.delete()) {
+ Log.d(TAG, "screenshot file: " + newFile + "deleted successfully.");
+ }
}
screenshotFiles = renamedFiles;
}
+ /**
+ * Rename bugreport file to include the name given by user via UI
+ */
+ void renameBugreportFile() {
+ File newBugreportFile = new File(bugreportFile.getParentFile(),
+ getFileName(this, ".zip"));
+ if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) {
+ if (bugreportFile.renameTo(newBugreportFile)) {
+ bugreportFile = newBugreportFile;
+ }
+ }
+ }
+
String getFormattedLastUpdate() {
if (context == null) {
// Restored from Parcel
return formattedLastUpdate == null ?
- Long.toString(lastUpdate) : formattedLastUpdate;
+ Long.toString(lastUpdate.longValue()) : formattedLastUpdate;
}
- return DateUtils.formatDateTime(context, lastUpdate,
+ return DateUtils.formatDateTime(context, lastUpdate.longValue(),
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
}
@Override
public String toString() {
- final float percent = ((float) progress * 100 / max);
- final float realPercent = ((float) realProgress * 100 / realMax);
final StringBuilder builder = new StringBuilder()
.append("\tid: ").append(id)
- .append(", pid: ").append(pid)
+ .append(", baseName: ").append(baseName)
.append(", name: ").append(name)
+ .append(", initialName: ").append(initialName)
.append(", finished: ").append(finished)
.append("\n\ttitle: ").append(title)
.append("\n\tdescription: ");
@@ -1812,14 +2074,12 @@ public class BugreportProgressService extends Service {
return builder
.append("\n\tfile: ").append(bugreportFile)
.append("\n\tscreenshots: ").append(screenshotFiles)
- .append("\n\tprogress: ").append(progress).append("/").append(max)
- .append(" (").append(percent).append(")")
- .append("\n\treal progress: ").append(realProgress).append("/").append(realMax)
- .append(" (").append(realPercent).append(")")
+ .append("\n\tprogress: ").append(progress)
.append("\n\tlast_update: ").append(getFormattedLastUpdate())
.append("\n\taddingDetailsToZip: ").append(addingDetailsToZip)
.append(" addedDetailsToZip: ").append(addedDetailsToZip)
.append("\n\tshareDescription: ").append(shareDescription)
+ .append("\n\tshareTitle: ").append(shareTitle)
.toString();
}
@@ -1827,15 +2087,13 @@ public class BugreportProgressService extends Service {
protected BugreportInfo(Parcel in) {
context = null;
id = in.readInt();
- pid = in.readInt();
+ baseName = in.readString();
name = in.readString();
+ initialName = in.readString();
title = in.readString();
description = in.readString();
- max = in.readInt();
- progress = in.readInt();
- realMax = in.readInt();
- realProgress = in.readInt();
- lastUpdate = in.readLong();
+ progress.set(in.readInt());
+ lastUpdate.set(in.readLong());
formattedLastUpdate = in.readString();
bugreportFile = readFile(in);
@@ -1844,23 +2102,23 @@ public class BugreportProgressService extends Service {
screenshotFiles.add(readFile(in));
}
- finished = in.readInt() == 1;
+ finished.set(in.readInt() == 1);
screenshotCounter = in.readInt();
shareDescription = in.readString();
+ shareTitle = in.readString();
+ type = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
- dest.writeInt(pid);
+ dest.writeString(baseName);
dest.writeString(name);
+ dest.writeString(initialName);
dest.writeString(title);
dest.writeString(description);
- dest.writeInt(max);
- dest.writeInt(progress);
- dest.writeInt(realMax);
- dest.writeInt(realProgress);
- dest.writeLong(lastUpdate);
+ dest.writeInt(progress.intValue());
+ dest.writeLong(lastUpdate.longValue());
dest.writeString(getFormattedLastUpdate());
writeFile(dest, bugreportFile);
@@ -1869,9 +2127,11 @@ public class BugreportProgressService extends Service {
writeFile(dest, screenshotFile);
}
- dest.writeInt(finished ? 1 : 0);
+ dest.writeInt(finished.get() ? 1 : 0);
dest.writeInt(screenshotCounter);
dest.writeString(shareDescription);
+ dest.writeString(shareTitle);
+ dest.writeInt(type);
}
@Override
@@ -1901,96 +2161,22 @@ public class BugreportProgressService extends Service {
return new BugreportInfo[size];
}
};
-
}
- private final class DumpstateListener extends IDumpstateListener.Stub
- implements DeathRecipient {
-
- private final BugreportInfo info;
- private IDumpstateToken token;
-
- DumpstateListener(BugreportInfo info) {
- this.info = info;
+ @GuardedBy("mLock")
+ private void checkProgressUpdatedLocked(BugreportInfo info, int progress) {
+ if (progress > CAPPED_PROGRESS) {
+ progress = CAPPED_PROGRESS;
}
-
- /**
- * Connects to the {@code dumpstate} binder to receive updates.
- */
- boolean connect() {
- if (token != null) {
- Log.d(TAG, "connect(): " + info.id + " already connected");
- return true;
- }
- final IBinder service = ServiceManager.getService("dumpstate");
- if (service == null) {
- Log.d(TAG, "dumpstate service not bound yet");
- return true;
+ if (DEBUG) {
+ if (progress != info.progress.intValue()) {
+ Log.v(TAG, "Updating progress for name " + info.getName() + "(id: " + info.id
+ + ") from " + info.progress.intValue() + " to " + progress);
}
- final IDumpstate dumpstate = IDumpstate.Stub.asInterface(service);
- try {
- token = dumpstate.setListener("Shell", this, /* perSectionDetails= */ false);
- if (token != null) {
- token.asBinder().linkToDeath(this, 0);
- }
- } catch (Exception e) {
- Log.e(TAG, "Could not set dumpstate listener: " + e);
- }
- return token != null;
}
+ info.progress.set(progress);
+ info.lastUpdate.set(System.currentTimeMillis());
- @Override
- public void binderDied() {
- if (!info.finished) {
- // TODO: linkToDeath() might be called BEFORE Shell received the
- // BUGREPORT_FINISHED broadcast, in which case the statements below
- // spam logcat (but are harmless).
- // The right, long-term solution is to provide an onFinished() callback
- // on IDumpstateListener and call it instead of using a broadcast.
- Log.w(TAG, "Dumpstate process died:\n" + info);
- stopProgress(info.id);
- }
- token.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void onProgress(int progress) throws RemoteException {
- if (progress > CAPPED_PROGRESS) {
- progress = CAPPED_PROGRESS;
- }
- updateProgressInfo(progress, CAPPED_MAX);
- }
-
- @Override
- public void onError(int errorCode) throws RemoteException {
- // TODO(b/111441001): implement
- }
-
- @Override
- public void onFinished() throws RemoteException {
- // TODO(b/111441001): implement
- }
-
- public void dump(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("token: "); pw.println(token);
- }
-
- private void updateProgressInfo(int progress, int max) {
- if (DEBUG) {
- if (progress != info.progress) {
- Log.v(TAG, "Updating progress for PID " + info.pid + "(id: " + info.id
- + ") from " + info.progress + " to " + progress);
- }
- if (max != info.max) {
- Log.v(TAG, "Updating max progress for PID " + info.pid + "(id: " + info.id
- + ") from " + info.max + " to " + max);
- }
- }
- info.progress = progress;
- info.max = max;
- info.lastUpdate = System.currentTimeMillis();
-
- updateProgress(info);
- }
+ updateProgress(info);
}
}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
deleted file mode 100644
index 15ce90fa6418..000000000000
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.shell;
-
-import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
-import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT;
-import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
-import static com.android.shell.BugreportProgressService.getFileExtra;
-import static com.android.shell.BugreportProgressService.dumpIntent;
-
-import java.io.File;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.FileUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-/**
- * Receiver that handles finished bugreports, usually by attaching them to an
- * {@link Intent#ACTION_SEND_MULTIPLE}.
- */
-public class BugreportReceiver extends BroadcastReceiver {
- private static final String TAG = "BugreportReceiver";
-
- /**
- * 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;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "onReceive(): " + dumpIntent(intent));
- // Clean up older bugreports in background
- cleanupOldFiles(this, intent, INTENT_BUGREPORT_FINISHED, MIN_KEEP_COUNT, MIN_KEEP_AGE);
-
- // Delegate intent handling to service.
- Intent serviceIntent = new Intent(context, BugreportProgressService.class);
- serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
- context.startService(serviceIntent);
- }
-
- static void cleanupOldFiles(BroadcastReceiver br, Intent intent, String expectedAction,
- final int minCount, final long minAge) {
- if (!expectedAction.equals(intent.getAction())) {
- return;
- }
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- if (bugreportFile == null || !bugreportFile.exists()) {
- Log.e(TAG, "Not deleting old files because file " + bugreportFile + " doesn't exist");
- return;
- }
- final PendingResult result = br.goAsync();
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- FileUtils.deleteOlderFiles(bugreportFile.getParentFile(), minCount, minAge);
- } catch (RuntimeException e) {
- Log.e(TAG, "RuntimeException deleting old files", e);
- }
- result.finish();
- return null;
- }
- }.execute();
- }
-}
diff --git a/packages/Shell/src/com/android/shell/BugreportRequestedReceiver.java b/packages/Shell/src/com/android/shell/BugreportRequestedReceiver.java
new file mode 100644
index 000000000000..da919bef6387
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportRequestedReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT;
+import static com.android.shell.BugreportProgressService.dumpIntent;
+
+import android.annotation.RequiresPermission;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Receiver that listens to {@link Intent#INTENT_BUGREPORT_REQUESTED}
+ * and starts up BugreportProgressService to start a new bugreport
+ */
+public class BugreportRequestedReceiver extends BroadcastReceiver {
+ private static final String TAG = "BugreportRequestedReceiver";
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.TRIGGER_SHELL_BUGREPORT)
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "onReceive(): " + dumpIntent(intent));
+
+ // Delegate intent handling to service.
+ Intent serviceIntent = new Intent(context, BugreportProgressService.class);
+ Log.d(TAG, "onReceive() ACTION: " + serviceIntent.getAction());
+ serviceIntent.setAction(intent.getAction());
+ serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
+ context.startService(serviceIntent);
+ }
+}
diff --git a/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java b/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java
deleted file mode 100644
index 634c3b47c787..000000000000
--- a/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.shell;
-
-import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
-import static com.android.shell.BugreportProgressService.INTENT_REMOTE_BUGREPORT_FINISHED;
-import static com.android.shell.BugreportProgressService.getFileExtra;
-import static com.android.shell.BugreportProgressService.getUri;
-import static com.android.shell.BugreportReceiver.cleanupOldFiles;
-
-import java.io.File;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-
-/**
- * Receiver that handles finished remote bugreports, by re-sending
- * the intent with appended bugreport zip file URI.
- *
- * <p> Remote bugreport never contains a screenshot.
- */
-public class RemoteBugreportReceiver extends BroadcastReceiver {
-
- private static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
-
- /** Always keep just the last 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 MIN_KEEP_AGE = DateUtils.DAY_IN_MILLIS;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- cleanupOldFiles(this, intent, INTENT_REMOTE_BUGREPORT_FINISHED,
- REMOTE_BUGREPORT_FILES_AMOUNT, MIN_KEEP_AGE);
-
- final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
- final Uri bugreportUri = getUri(context, bugreportFile);
- final String bugreportHash = intent.getStringExtra(
- DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH);
-
- final Intent newIntent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH);
- newIntent.setDataAndType(bugreportUri, BUGREPORT_MIMETYPE);
- newIntent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
- context.sendBroadcastAsUser(newIntent, UserHandle.SYSTEM,
- android.Manifest.permission.DUMP);
- }
-}