Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 457ef13c authored by Felipe Leme's avatar Felipe Leme Committed by Android (Google) Code Review
Browse files

Merge "Refactored what happens when a BUGREPORT_FINISHED is received."

parents 1c7de373 46d47911
Loading
Loading
Loading
Loading
+161 −76
Original line number Diff line number Diff line
@@ -66,7 +66,7 @@ import android.util.SparseArray;
import android.widget.Toast;

/**
 * Service used to keep progress of bug reports processes ({@code dumpstate}).
 * Service used to keep progress of bugreport processes ({@code dumpstate}).
 * <p>
 * The workflow is:
 * <ol>
@@ -96,9 +96,13 @@ public class BugreportProgressService extends Service {

    private static final String AUTHORITY = "com.android.shell";

    // External intents sent by dumpstate.
    static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
    static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";

    // Internal intents used on notification actions.
    static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
    static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";

    static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
    static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
@@ -111,7 +115,7 @@ public class BugreportProgressService extends Service {
    private static final int MSG_POLL = 2;

    /** Polling frequency, in milliseconds. */
    private static final long POLLING_FREQUENCY = 500;
    static final long POLLING_FREQUENCY = 2000;

    /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
    private static final long INACTIVITY_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
@@ -169,10 +173,16 @@ public class BugreportProgressService extends Service {

    @Override
    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
        writer.printf("Monitored dumpstate processes: \n");
        synchronized (mProcesses) {
            for (int i = 0; i < mProcesses.size(); i++) {
              writer.printf("\t%s\n", mProcesses.valueAt(i));
            final int size = mProcesses.size();
            if (size == 0) {
                writer.printf("No monitored processes");
                return;
            }
            writer.printf("Monitored dumpstate processes\n");
            writer.printf("-----------------------------\n");
            for (int i = 0; i < size; i++) {
              writer.printf("%s\n", mProcesses.valueAt(i));
            }
        }
    }
@@ -180,7 +190,6 @@ public class BugreportProgressService extends Service {
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
            poll();
        }

        @Override
@@ -196,25 +205,24 @@ public class BugreportProgressService extends Service {
                return;
            }

            // At this point it's handling onStartCommand(), whose intent contains the extras
            // originally received by BugreportReceiver.
            // At this point it's handling onStartCommand(), with the intent passed as an Extra.
            if (!(msg.obj instanceof Intent)) {
                // Sanity check.
                Log.e(TAG, "Internal error: invalid msg.obj: " + msg.obj);
                return;
            }
            final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
            if (!(parcel instanceof Intent)) {
                // Sanity check.
                Log.e(TAG, "Internal error: msg.obj is missing extra " + EXTRA_ORIGINAL_INTENT);
                return;
            final Intent intent;
            if (parcel instanceof Intent) {
                // The real intent was passed to BugreportReceiver, which delegated to the service.
                intent = (Intent) parcel;
            } else {
                intent = (Intent) msg.obj;
            }

            final Intent intent = (Intent) parcel;
            final String action = intent.getAction();
            int pid = intent.getIntExtra(EXTRA_PID, 0);
            int max = intent.getIntExtra(EXTRA_MAX, -1);
            String name = intent.getStringExtra(EXTRA_NAME);
            final int pid = intent.getIntExtra(EXTRA_PID, 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 + ", pid: " + pid
                    + ", max: "+ max);
@@ -224,14 +232,18 @@ public class BugreportProgressService extends Service {
                        stopSelfWhenDone();
                        return;
                    }
                    poll();
                    break;
                case INTENT_BUGREPORT_FINISHED:
                    if (pid == -1) {
                    if (pid == 0) {
                        // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
                        // out-of-sync dumpstate process.
                        Log.w(TAG, "Missing " + EXTRA_PID + " on intent " + intent);
                    }
                    stopProgress(pid, intent);
                    onBugreportFinished(pid, intent);
                    break;
                case INTENT_BUGREPORT_SHARE:
                    shareBugreport(pid);
                    break;
                case INTENT_BUGREPORT_CANCEL:
                    cancel(pid);
@@ -247,6 +259,8 @@ public class BugreportProgressService extends Service {
            if (pollProgress()) {
                // Keep polling...
                sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
            } else {
                Log.i(TAG, "Stopped polling");
            }
        }
    }
@@ -296,14 +310,8 @@ public class BugreportProgressService extends Service {
        nf.setMinimumFractionDigits(2);
        nf.setMaximumFractionDigits(2);
        final String percentText = nf.format((double) info.progress / info.max);

        final Intent cancelIntent = new Intent(context, BugreportReceiver.class);
        cancelIntent.setAction(INTENT_BUGREPORT_CANCEL);
        cancelIntent.putExtra(EXTRA_PID, info.pid);
        final Action cancelAction = new Action.Builder(null,
                context.getString(com.android.internal.R.string.cancel),
                PendingIntent.getBroadcast(context, info.pid, cancelIntent,
                        PendingIntent.FLAG_CANCEL_CURRENT)).build();
        final Action cancelAction = new Action.Builder(null, context.getString(
                com.android.internal.R.string.cancel), newCancelIntent(context, info)).build();

        final String title = context.getString(R.string.bugreport_in_progress_title);
        final String name =
@@ -327,9 +335,19 @@ public class BugreportProgressService extends Service {
    }

    /**
     * Finalizes the progress on a given process and sends the finished intent.
     * Creates a {@link PendingIntent} for a notification action used to cancel a bugreport.
     */
    private void stopProgress(int pid, Intent intent) {
    private static PendingIntent newCancelIntent(Context context, BugreportInfo info) {
        final Intent intent = new Intent(INTENT_BUGREPORT_CANCEL);
        intent.setClass(context, BugreportProgressService.class);
        intent.putExtra(EXTRA_PID, info.pid);
        return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
    }

    /**
     * Finalizes the progress on a given bugreport and cancel its notification.
     */
    private void stopProgress(int pid) {
        synchronized (mProcesses) {
            if (mProcesses.indexOfKey(pid) < 0) {
                Log.w(TAG, "PID not watched: " + pid);
@@ -340,11 +358,6 @@ public class BugreportProgressService extends Service {
        }
        if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
        NotificationManager.from(getApplicationContext()).cancel(TAG, pid);
        if (intent != null) {
            // Bug report finished fine: send a new, different notification.
            if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): finish bug report");
            onBugreportFinished(pid, intent);
        }
    }

    /**
@@ -353,7 +366,7 @@ public class BugreportProgressService extends Service {
    private void cancel(int pid) {
        Log.i(TAG, "Cancelling PID " + pid + " on user's request");
        SystemProperties.set(CTL_STOP, BUGREPORT_SERVICE);
        stopProgress(pid, null);
        stopProgress(pid);
    }

    /**
@@ -363,11 +376,19 @@ public class BugreportProgressService extends Service {
     */
    private boolean pollProgress() {
        synchronized (mProcesses) {
            if (mProcesses.size() == 0) {
            final int total = mProcesses.size();
            if (total == 0) {
                Log.d(TAG, "No process to poll progress.");
            }
            for (int i = 0; i < mProcesses.size(); i++) {
            int activeProcesses = 0;
            for (int i = 0; i < total; i++) {
                final int pid = mProcesses.keyAt(i);
                final BugreportInfo info = mProcesses.valueAt(i);
                if (info.finished) {
                    if (DEBUG) Log.v(TAG, "Skipping finished process " + pid);
                    continue;
                }
                activeProcesses++;
                final String progressKey = DUMPSTATE_PREFIX + pid + PROGRESS_SUFFIX;
                final int progress = SystemProperties.getInt(progressKey, 0);
                if (progress == 0) {
@@ -375,7 +396,6 @@ public class BugreportProgressService extends Service {
                    continue;
                }
                final int max = SystemProperties.getInt(DUMPSTATE_PREFIX + pid + MAX_SUFFIX, 0);
                final BugreportInfo info = mProcesses.valueAt(i);
                final boolean maxChanged = max > 0 && max != info.max;
                final boolean progressChanged = progress > 0 && progress != info.progress;

@@ -397,11 +417,12 @@ public class BugreportProgressService extends Service {
                    if (inactiveTime >= INACTIVITY_TIMEOUT) {
                        Log.w(TAG, "No progress update for process " + pid + " since "
                                + info.getFormattedLastUpdate());
                        stopProgress(info.pid, null);
                        stopProgress(info.pid);
                    }
                }
            }
            return true;
            if (DEBUG) Log.v(TAG, "pollProgress() total=" + total + ", actives=" + activeProcesses);
            return activeProcesses > 0;
        }
    }

@@ -421,12 +442,22 @@ public class BugreportProgressService extends Service {

    private void onBugreportFinished(int pid, Intent intent) {
        final Context context = getApplicationContext();
        final Configuration conf = context.getResources().getConfiguration();
        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
        final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
        BugreportInfo info;
        synchronized (mProcesses) {
            info = mProcesses.get(pid);
            if (info == null) {
                // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED
                Log.v(TAG, "Creating info for untracked pid " + pid);
                info = new BugreportInfo(context, pid);
                mProcesses.put(pid, info);
            }
            info.bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
            info.screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
        }

        final Configuration conf = context.getResources().getConfiguration();
        if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
            triggerLocalNotification(context, pid, bugreportFile, screenshotFile);
            triggerLocalNotification(context, info);
        }
    }

@@ -436,22 +467,21 @@ public class BugreportProgressService extends Service {
     * (usually by triggering it on another connected device); we don't need to display the
     * notification in this case.
     */
    private static void triggerLocalNotification(final Context context, final int pid,
            final File bugreportFile, final File screenshotFile) {
        if (!bugreportFile.exists() || !bugreportFile.canRead()) {
            Log.e(TAG, "Could not read bugreport file " + bugreportFile);
    private static void triggerLocalNotification(final Context context, final BugreportInfo info) {
        if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
            Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
            Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
                    Toast.LENGTH_LONG).show();
            return;
        }

        boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
        boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
        if (!isPlainText) {
            // Already zipped, send it right away.
            sendBugreportNotification(context, pid, bugreportFile, screenshotFile);
            sendBugreportNotification(context, info);
        } else {
            // Asynchronously zip the file first, then send it.
            sendZippedBugreportNotification(context, pid, bugreportFile, screenshotFile);
            sendZippedBugreportNotification(context, info);
        }
    }

@@ -498,17 +528,27 @@ public class BugreportProgressService extends Service {
    }

    /**
     * Sends a bugreport notitication.
     * Shares the bugreport upon user's request by issuing a {@link Intent#ACTION_SEND_MULTIPLE}
     * intent, but issuing a warning dialog the first time.
     */
    private static void sendBugreportNotification(Context context, int pid, File bugreportFile,
            File screenshotFile) {
    private void shareBugreport(int pid) {
        final Context context = getApplicationContext();
        final BugreportInfo info;
        synchronized (mProcesses) {
            info = mProcesses.get(pid);
            if (info == null) {
                // Should not happen, so log if it does...
                Log.e(TAG, "INTERNAL ERROR: no info for PID " + pid + ": " + mProcesses);
                return;
            }
        }
        // Files are kept on private storage, so turn into Uris that we can
        // grant temporary permissions for.
        final Uri bugreportUri = getUri(context, bugreportFile);
        final Uri screenshotUri = getUri(context, screenshotFile);
        final Uri bugreportUri = getUri(context, info.bugreportFile);
        final Uri screenshotUri = getUri(context, info.screenshotFile);

        Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
        Intent notifIntent;
        final Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
        final Intent notifIntent;

        // Send through warning dialog by default
        if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
@@ -518,32 +558,48 @@ public class BugreportProgressService extends Service {
        }
        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        // Send the share intent...
        context.startActivity(notifIntent);

        // ... and stop watching this process.
        stopProgress(pid);
    }

    /**
     * Sends a notitication indicating the bugreport has finished so use can share it.
     */
    private static void sendBugreportNotification(Context context, BugreportInfo info) {
        final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE);
        shareIntent.setClass(context, BugreportProgressService.class);
        shareIntent.setAction(INTENT_BUGREPORT_SHARE);
        shareIntent.putExtra(EXTRA_PID, info.pid);

        final String title = context.getString(R.string.bugreport_finished_title);
        final Notification.Builder builder = new Notification.Builder(context)
                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                .setContentTitle(title)
                .setTicker(title)
                .setContentText(context.getString(R.string.bugreport_finished_text))
                .setContentIntent(PendingIntent.getActivity(
                        context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
                .setAutoCancel(true)
                .setContentIntent(PendingIntent.getService(context, 0, shareIntent,
                        PendingIntent.FLAG_CANCEL_CURRENT))
                .setDeleteIntent(newCancelIntent(context, info))
                .setLocalOnly(true)
                .setColor(context.getColor(
                        com.android.internal.R.color.system_notification_accent_color));

        NotificationManager.from(context).notify(TAG, pid, builder.build());
        NotificationManager.from(context).notify(TAG, info.pid, builder.build());
    }

    /**
     * Sends a zipped bugreport notification.
     */
    private static void sendZippedBugreportNotification(final Context context,
            final int pid, final File bugreportFile, final File screenshotFile) {
            final BugreportInfo info) {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                File zippedFile = zipBugreport(bugreportFile);
                sendBugreportNotification(context, pid, zippedFile, screenshotFile);
                info.bugreportFile = zipBugreport(info.bugreportFile);
                sendBugreportNotification(context, info);
                return null;
            }
        }.execute();
@@ -662,6 +718,24 @@ public class BugreportProgressService extends Service {
         */
        long lastUpdate = System.currentTimeMillis();

        /**
         * Path of the main bugreport file.
         */
        File bugreportFile;

        /**
         * Path of the screenshot file.
         */
        File screenshotFile;

        /**
         * Whether dumpstate sent an intent informing it has finished.
         */
        boolean finished;

        /**
         * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
         */
        BugreportInfo(Context context, int pid, String name, int max) {
            this.context = context;
            this.pid = pid;
@@ -669,6 +743,15 @@ public class BugreportProgressService extends Service {
            this.max = max;
        }

        /**
         * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
         * without a previous call to BUGREPORT_STARTED.
         */
        BugreportInfo(Context context, int pid) {
            this(context, pid, null, 0);
            this.finished = true;
        }

        String getFormattedLastUpdate() {
            return DateUtils.formatDateTime(context, lastUpdate,
                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
@@ -677,8 +760,10 @@ public class BugreportProgressService extends Service {
        @Override
        public String toString() {
            final float percent = ((float) progress * 100 / max);
            return "Progress for " + name + " (pid=" + pid + "): " + progress + "/" + max
                    + " (" + percent + "%) Last update: " + getFormattedLastUpdate();
            return "pid: " + pid + ", name: " + name + ", finished: " + finished
                    + "\n\tfile: " + bugreportFile + "\n\tscreenshot: " + screenshotFile
                    + "\n\tprogress: " + progress + "/" + max + "(" + percent + ")"
                    + "\n\tlast_update: " + getFormattedLastUpdate();
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -80,7 +80,7 @@ public class BugreportReceiverTest extends InstrumentationTestCase {
    private static final String TAG = "BugreportReceiverTest";

    // Timeout for UI operations, in milliseconds.
    private static final int TIMEOUT = 1000;
    private static final int TIMEOUT = (int) BugreportProgressService.POLLING_FREQUENCY * 4;

    private static final String ROOT_DIR = "/data/data/com.android.shell/files/bugreports";
    private static final String BUGREPORT_FILE = "test_bugreport.txt";