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.java840
-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, 508 insertions, 534 deletions
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 29c61ee70f7d..30ad9c5d658c 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,34 +76,54 @@ 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.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.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:
- * <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:
+ * The workflow uses Bugreport API({@code BugreportManager}) and is as follows:
* <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.
@@ -139,15 +132,17 @@ 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,9 +153,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";
@@ -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,34 @@ public class BugreportProgressService extends Service {
* <p>
* Must be a path supported by its FileProvider.
*/
+ // TODO: use the same variable for both dir
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) */
+ private final SparseArray<BugreportInfo> mBugreportInfos = new SparseArray<>();
private Context mContext;
@@ -224,6 +234,8 @@ public class BugreportProgressService extends Service {
private File mScreenshotsDir;
+ private BugreportManager mBugreportManager;
+
/**
* id of the notification used to set service on foreground.
*/
@@ -251,6 +263,7 @@ public class BugreportProgressService extends Service {
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()) {
@@ -277,6 +290,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,7 +319,7 @@ public class BugreportProgressService extends Service {
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- final int size = mProcesses.size();
+ final int size = mBugreportInfos.size();
if (size == 0) {
writer.println("No monitored processes");
return;
@@ -314,8 +330,158 @@ public class BugreportProgressService extends Service {
writer.println("-----------------------------");
for (int i = 0; i < size; i++) {
writer.print("#"); writer.println(i + 1);
- writer.println(mProcesses.valueAt(i).info);
+ writer.println(getInfo(mBugreportInfos.keyAt(i)));
+ }
+ }
+
+ private static String getFileName(BugreportInfo info, String suffix) {
+ return String.format("%s-%s%s", info.baseName, info.name, 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) {
+ if (progress == 0) {
+ trackInfoWithIdLocked();
+ }
+ 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) {
+ trackInfoWithIdLocked();
+ stopProgressLocked(mInfo.id);
+ }
+ Log.e(TAG, "Bugreport API callback onError() errorCode = " + errorCode);
+ return;
+ }
+
+ @Override
+ public void onFinished() {
+ mInfo.renameBugreportFile();
+ mInfo.renameScreenshots(mScreenshotsDir);
+ synchronized (mLock) {
+ sendBugreportFinishedBroadcastLocked();
+ }
}
+
+ /**
+ * Reads bugreport id and links it to the bugreport info to track the bugreport's
+ * progress/completion/error. id is incremented in dumpstate code. This function is called
+ * when dumpstate calls one of the callback functions (onProgress, onFinished, onError)
+ * after the id has been incremented.
+ */
+ @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;
+ }
+
+ @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 {
+ trackInfoWithIdLocked();
+ cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE);
+ 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);
+ 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) {
+ 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();
}
/**
@@ -354,34 +520,23 @@ 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
+ + ", max: " + max);
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);
@@ -422,52 +577,85 @@ 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);
+ 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");
- }
- if (id == -1) {
- Log.e(TAG, "Missing " + EXTRA_ID + " on start intent");
- return false;
+ 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 (pid == -1) {
- Log.e(TAG, "Missing " + EXTRA_PID + " 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,
+ 100 /* max progress*/, shareTitle, shareDescription, bugreportType);
+
+ ParcelFileDescriptor bugreportFd = info.createBugreportFd();
+ if (bugreportFd == null) {
+ Log.e(TAG, "Bugreport parcel file descriptor is null.");
+ return;
}
- if (max <= 0) {
- Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
- return false;
+ ParcelFileDescriptor screenshotFd = info.createScreenshotFd();
+ if (screenshotFd == null) {
+ Log.e(TAG, "Screenshot parcel file descriptor is null. Deleting bugreport file");
+ FileUtils.closeQuietly(bugreportFd);
+ info.bugreportFile.delete();
+ return;
}
+ mBugreportManager = (BugreportManager) mContext.getSystemService(
+ Context.BUGREPORT_SERVICE);
+ final Executor executor = ActivityThread.currentActivityThread().getExecutor();
- 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;
+ Log.i(TAG, "bugreport type = " + bugreportType
+ + " bugreport file fd: " + bugreportFd
+ + " screenshot file fd: " + screenshotFd);
+
+ BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(info);
+ try {
+ mBugreportManager.startBugreport(bugreportFd, screenshotFd,
+ new BugreportParams(bugreportType), executor, bugreportCallback);
+ } 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);
+ FileUtils.closeQuietly(screenshotFd);
}
- 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;
+ }
+
+ private static ParcelFileDescriptor createReadWriteFile(File file) {
+ try {
+ file.createNewFile();
+ file.setReadable(true, true);
+ file.setWritable(true, true);
+
+ ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
+ return fd;
+ } catch (IOException e) {
+ Log.i(TAG, "Error in generating bugreports: ", e);
}
+ return null;
}
/**
@@ -554,6 +742,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,12 +763,13 @@ 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);
@@ -596,10 +788,12 @@ public class BugreportProgressService extends Service {
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);
+ mBugreportManager.cancelBugreport();
deleteScreenshots(info);
}
- stopProgress(id);
+ synchronized (mLock) {
+ stopProgressLocked(id);
+ }
}
/**
@@ -697,8 +891,8 @@ public class BugreportProgressService extends Service {
private void setTakingScreenshot(boolean flag) {
synchronized (BugreportProgressService.this) {
mTakingScreenshot = flag;
- for (int i = 0; i < mProcesses.size(); i++) {
- final BugreportInfo info = mProcesses.valueAt(i).info;
+ for (int i = 0; i < mBugreportInfos.size(); i++) {
+ final BugreportInfo info = getInfo(mBugreportInfos.keyAt(i));
if (info.finished) {
Log.d(TAG, "Not updating progress for " + info.id + " while taking screenshot"
+ " because share notification was already sent");
@@ -767,10 +961,10 @@ 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;
+ final BugreportInfo info = getInfo(mBugreportInfos.keyAt(i));
if (!info.finished) {
updateProgress(info);
break;
@@ -783,8 +977,8 @@ 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);
+ 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 +986,15 @@ 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);
- }
-
- 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);
- }
+ private void onBugreportFinished(BugreportInfo info) {
+ Log.d(TAG, "Bugreport finished with title: " + info.title
+ + " and shareDescription: " + info.shareDescription);
info.finished = true;
// Stop running on foreground, otherwise share notification cannot be dismissed.
- stopForegroundWhenDone(id);
+ stopForegroundWhenDone(info.id);
triggerLocalNotification(mContext, info);
}
@@ -858,7 +1009,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;
}
@@ -881,7 +1034,11 @@ 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) {
+ private static Intent buildSendIntent(Context context, BugreportInfo info,
+ File screenshotsDir) {
+ // Rename files (if required) before sharing
+ info.renameBugreportFile();
+ info.renameScreenshots(screenshotsDir);
// Files are kept on private storage, so turn into Uris that we can
// grant temporary permissions for.
final Uri bugreportUri;
@@ -960,17 +1117,19 @@ public class BugreportProgressService extends Service {
// 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 + ")");
+ + mBugreportInfos + "), using info from intent instead (" + info + ")");
} else {
Log.v(TAG, "shareBugReport(): id " + id + " info = " + info);
}
addDetailsToZipFile(info);
- final Intent sendIntent = buildSendIntent(mContext, info);
+ final Intent sendIntent = buildSendIntent(mContext, info, mScreenshotsDir);
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 +1152,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) {
@@ -1292,12 +1452,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);
@@ -1342,15 +1501,6 @@ 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) {
@@ -1434,30 +1584,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 +1603,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 +1617,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,9 +1635,7 @@ 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);
}
@@ -1555,7 +1662,7 @@ public class BugreportProgressService extends Service {
@Override
public void onClick(View view) {
MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
- sanitizeName();
+ sanitizeName(info.name);
final String name = mInfoName.getText().toString();
final String title = mInfoTitle.getText().toString();
final String description = mInfoDescription.getText().toString();
@@ -1571,9 +1678,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 +1700,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,28 +1718,43 @@ 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;
+ 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;
/**
+ * 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 initialName;
+
+ /**
* 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 title;
/**
+ * 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.
+ */
+ String shareTitle;
+
+ /**
* User-provided, detailed description of the bugreport; when set, will be added to the body
* of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
*/
@@ -1714,36 +1817,56 @@ 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;
/**
- * 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) {
+ int type;
+
+ /**
+ * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
+ */
+ BugreportInfo(Context context, String baseName, String name, int max,
+ @Nullable String shareTitle, @Nullable String shareDescription,
+ @BugreportParams.BugreportMode int type) {
this.context = context;
- this.id = id;
- this.pid = pid;
- this.name = name;
+ this.name = this.initialName = name;
this.max = this.realMax = max;
+ this.shareTitle = shareTitle == null ? "" : shareTitle;
+ this.shareDescription = shareDescription == null ? "" : shareDescription;
+ this.type = type;
+ this.baseName = baseName;
}
- /**
- * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
- * without a previous call to BUGREPORT_STARTED.
- */
- BugreportInfo(Context context, int id) {
- this(context, id, id, null, 0);
- this.finished = true;
+ ParcelFileDescriptor createBugreportFd() {
+ bugreportFile = new File(BUGREPORT_DIR, getFileName(this, ".zip"));
+ return createReadWriteFile(bugreportFile);
+ }
+
+ ParcelFileDescriptor createScreenshotFd() {
+ File screenshotFile = new File(BUGREPORT_DIR, getScreenshotName("default"));
+ addScreenshot(screenshotFile);
+ return createReadWriteFile(screenshotFile);
}
/**
- * 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,7 +1877,8 @@ public class BugreportProgressService extends Service {
}
/**
- * Rename all screenshots files so that they contain the user-generated name instead of pid.
+ * 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) {
if (TextUtils.isEmpty(name)) {
@@ -1763,21 +1887,37 @@ public class BugreportProgressService extends Service {
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);
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(BUGREPORT_DIR, getFileName(this, ".zip"));
+ if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) {
+ if (bugreportFile.renameTo(newBugreportFile)) {
+ bugreportFile = newBugreportFile;
+ }
+ }
+ }
+
String getFormattedLastUpdate() {
if (context == null) {
// Restored from Parcel
@@ -1795,8 +1935,9 @@ public class BugreportProgressService extends Service {
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: ");
@@ -1820,6 +1961,7 @@ public class BugreportProgressService extends Service {
.append("\n\taddingDetailsToZip: ").append(addingDetailsToZip)
.append(" addedDetailsToZip: ").append(addedDetailsToZip)
.append("\n\tshareDescription: ").append(shareDescription)
+ .append("\n\tshareTitle: ").append(shareTitle)
.toString();
}
@@ -1827,8 +1969,9 @@ 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();
@@ -1847,13 +1990,15 @@ public class BugreportProgressService extends Service {
finished = in.readInt() == 1;
screenshotCounter = in.readInt();
shareDescription = in.readString();
+ shareTitle = in.readString();
}
@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);
@@ -1872,6 +2017,7 @@ public class BugreportProgressService extends Service {
dest.writeInt(finished ? 1 : 0);
dest.writeInt(screenshotCounter);
dest.writeString(shareDescription);
+ dest.writeString(shareTitle);
}
@Override
@@ -1904,93 +2050,29 @@ public class BugreportProgressService extends Service {
}
- private final class DumpstateListener extends IDumpstateListener.Stub
- implements DeathRecipient {
-
- private final BugreportInfo info;
- private IDumpstateToken token;
-
- DumpstateListener(BugreportInfo info) {
- this.info = info;
- }
-
- /**
- * 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;
- }
- 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;
+ @GuardedBy("mLock")
+ private void checkProgressUpdatedLocked(BugreportInfo info, int progress) {
+ if (progress > CAPPED_PROGRESS) {
+ progress = CAPPED_PROGRESS;
}
+ updateProgressInfo(info, progress, CAPPED_MAX);
+ }
- @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);
+ private void updateProgressInfo(BugreportInfo info, int progress, int max) {
+ if (DEBUG) {
+ if (progress != info.progress) {
+ Log.v(TAG, "Updating progress for name " + info.name + "(id: " + info.id
+ + ") from " + info.progress + " to " + progress);
}
- token.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void onProgress(int progress) throws RemoteException {
- if (progress > CAPPED_PROGRESS) {
- progress = CAPPED_PROGRESS;
+ if (max != info.max) {
+ Log.v(TAG, "Updating max progress for name " + info.name + "(id: " + info.id
+ + ") from " + info.max + " to " + max);
}
- 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
}
+ info.progress = progress;
+ info.max = max;
+ info.lastUpdate = System.currentTimeMillis();
- 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);
- }
-}