diff options
Diffstat (limited to 'packages/Shell/src')
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); - } -} |