BugreportProgressService.java revision d1e0f12979441733753b538611f6d73e5527c43c
1b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme/*
2b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * Copyright (C) 2015 The Android Open Source Project
3b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme *
4b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * Licensed under the Apache License, Version 2.0 (the "License");
5b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * you may not use this file except in compliance with the License.
6b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * You may obtain a copy of the License at
7b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme *
8b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme *      http://www.apache.org/licenses/LICENSE-2.0
9b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme *
10b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * Unless required by applicable law or agreed to in writing, software
11b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * distributed under the License is distributed on an "AS IS" BASIS,
12b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * See the License for the specific language governing permissions and
14b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme * limitations under the License.
15b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme */
16b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
17b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemepackage com.android.shell;
18b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
19d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport static android.os.Process.THREAD_PRIORITY_BACKGROUND;
20b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport static com.android.shell.BugreportPrefs.STATE_SHOW;
21b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport static com.android.shell.BugreportPrefs.getWarningState;
22b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
23b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.BufferedOutputStream;
24b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.File;
2569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.io.FileDescriptor;
26b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.FileInputStream;
27b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.FileOutputStream;
28b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.IOException;
29b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.InputStream;
3069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.io.PrintWriter;
3169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.text.NumberFormat;
32b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.ArrayList;
33d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport java.util.List;
34b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.zip.ZipEntry;
35b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.zip.ZipOutputStream;
36b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
37b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport libcore.io.Streams;
38b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
39bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport com.android.internal.annotations.VisibleForTesting;
40b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport com.google.android.collect.Lists;
41b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
42b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.accounts.Account;
43b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.accounts.AccountManager;
44bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.annotation.SuppressLint;
45bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.app.AlertDialog;
46b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.Notification;
479cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Lemeimport android.app.Notification.Action;
48b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.NotificationManager;
49b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.PendingIntent;
50b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.Service;
51b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.ClipData;
52b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.Context;
53d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport android.content.ContextWrapper;
54bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.content.DialogInterface;
55b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.Intent;
56b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.res.Configuration;
57b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.net.Uri;
58b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.AsyncTask;
5969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Handler;
6069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.HandlerThread;
61b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.IBinder;
6269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Looper;
6369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Message;
6469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Parcelable;
65b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.SystemProperties;
66d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport android.os.Vibrator;
67b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.support.v4.content.FileProvider;
68bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.text.TextUtils;
6969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.text.format.DateUtils;
70b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.util.Log;
71b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.util.Patterns;
7269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.util.SparseArray;
73bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.View;
74bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.WindowManager;
75bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.View.OnFocusChangeListener;
76bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.inputmethod.EditorInfo;
77bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.widget.Button;
78bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.widget.EditText;
79b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.widget.Toast;
80b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
8169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme/**
8246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme * Service used to keep progress of bugreport processes ({@code dumpstate}).
8369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <p>
8469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * The workflow is:
8569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
8669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with its pid and the
8769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * estimated total effort.
8869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
8969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Upon start, this service:
9069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
9169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
9269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
9369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>If the progress changed, it updates the system notification.
9469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
9569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>As {@code dumpstate} progresses, it updates the system property.
9669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
9769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
9869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * turn:
9969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
10046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme * <li>Updates the system notification so user can share the bugreport.
10169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Stops monitoring that {@code dumpstate} process.
10269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Stops itself if it doesn't have any process left to monitor.
10369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
10469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
10569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme */
106b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemepublic class BugreportProgressService extends Service {
107a0bf0336f0b6ff39cd90aabe0eb48b022d008ed6Felipe Leme    static final String TAG = "Shell";
10869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final boolean DEBUG = false;
109b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
110b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static final String AUTHORITY = "com.android.shell";
111b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
11246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    // External intents sent by dumpstate.
11369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
11469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";
11546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
11646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    // Internal intents used on notification actions.
1179cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme    static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
11846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
119bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String INTENT_BUGREPORT_INFO_LAUNCH =
120bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            "android.intent.action.BUGREPORT_INFO_LAUNCH";
121d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    static final String INTENT_BUGREPORT_SCREENSHOT =
122d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            "android.intent.action.BUGREPORT_SCREENSHOT";
12369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
124b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
125b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
12669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_PID = "android.intent.extra.PID";
12769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_MAX = "android.intent.extra.MAX";
12869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_NAME = "android.intent.extra.NAME";
129bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String EXTRA_TITLE = "android.intent.extra.TITLE";
130bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
13169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
13269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
13369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final int MSG_SERVICE_COMMAND = 1;
13469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final int MSG_POLL = 2;
135d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_DELAYED_SCREENSHOT = 3;
136d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_SCREENSHOT_REQUEST = 4;
137d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_SCREENSHOT_RESPONSE = 5;
138d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
139d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
140d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Delay before a screenshot is taken.
141d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
142d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Should be at least 3 seconds, otherwise its toast might show up in the screenshot.
143d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
144d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    static final int SCREENSHOT_DELAY_SECONDS = 3;
14569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
14669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** Polling frequency, in milliseconds. */
147bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final long POLLING_FREQUENCY = 2 * DateUtils.SECOND_IN_MILLIS;
14869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
14969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
15069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final long INACTIVITY_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
15169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
152719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    /** System properties used for monitoring progress. */
153719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String DUMPSTATE_PREFIX = "dumpstate.";
154719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String PROGRESS_SUFFIX = ".progress";
155719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String MAX_SUFFIX = ".max";
156bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static final String NAME_SUFFIX = ".name";
1579cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme
158bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /** System property (and value) used to stop dumpstate. */
159d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    // TODO: should call ActiveManager API instead
160719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String CTL_STOP = "ctl.stop";
1614cc863338d5e43b6189e05498d7cb53ebba135e1Felipe Leme    private static final String BUGREPORT_SERVICE = "bugreportplus";
16269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
163d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
164d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Directory on Shell's data storage where screenshots will be stored.
165d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
166d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Must be a path supported by its FileProvider.
167d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
168d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final String SCREENSHOT_DIR = "bugreports";
169d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
17069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** Managed dumpstate processes (keyed by pid) */
17169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>();
17269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
173d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private Context mContext;
174d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private ServiceHandler mMainHandler;
175d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private ScreenshotHandler mScreenshotHandler;
17669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
177bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog();
178bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
179d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private File mScreenshotsDir;
180d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
181d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
182d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Flag indicating whether a screenshot is being taken.
183d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
184d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * This is the only state that is shared between the 2 handlers and hence must have synchronized
185d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * access.
186d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
187d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private boolean mTakingScreenshot;
188d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
18969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
19069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void onCreate() {
191d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mContext = getApplicationContext();
192d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler = new ServiceHandler("BugreportProgressServiceMainThread");
193d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
194d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
195d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotsDir = new File(new ContextWrapper(mContext).getFilesDir(), SCREENSHOT_DIR);
196d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (!mScreenshotsDir.exists()) {
197d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots");
198d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (!mScreenshotsDir.mkdir()) {
199d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.w(TAG, "Could not create directory " + mScreenshotsDir);
200d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
201d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
20269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
203b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
204b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    @Override
205b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    public int onStartCommand(Intent intent, int flags, int startId) {
206b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (intent != null) {
20769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            // Handle it in a separate thread.
208d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Message msg = mMainHandler.obtainMessage();
20969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            msg.what = MSG_SERVICE_COMMAND;
21069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            msg.obj = intent;
211d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mMainHandler.sendMessage(msg);
212b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
21369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
21469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        // If service is killed it cannot be recreated because it would not know which
21569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        // dumpstate PIDs it would have to watch.
216b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return START_NOT_STICKY;
217b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
218b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
219b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    @Override
220b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    public IBinder onBind(Intent intent) {
221b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return null;
222b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
223b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
22469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
22569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void onDestroy() {
226d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler.getLooper().quit();
227d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler.getLooper().quit();
22869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        super.onDestroy();
22969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
230b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
23169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
23269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
233d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int size = mProcesses.size();
234d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (size == 0) {
235d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            writer.printf("No monitored processes");
236d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
237d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
238d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        writer.printf("Monitored dumpstate processes\n");
239d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        writer.printf("-----------------------------\n");
240d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (int i = 0; i < size; i++) {
241d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            writer.printf("%s\n", mProcesses.valueAt(i));
24269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
24369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
24469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
245d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
246d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Main thread used to handle all requests but taking screenshots.
247d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
24869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private final class ServiceHandler extends Handler {
249d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public ServiceHandler(String name) {
250d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            super(newLooper(name));
25169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
25269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
25369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        @Override
25469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        public void handleMessage(Message msg) {
25569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (msg.what == MSG_POLL) {
256923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                poll();
25769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
25869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
25969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
260d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what == MSG_DELAYED_SCREENSHOT) {
261d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                takeScreenshot(msg.arg1, msg.arg2);
262d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
263d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
264d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
265d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what == MSG_SCREENSHOT_RESPONSE) {
266d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                handleScreenshotResponse(msg);
267d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
268d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
269d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
27069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (msg.what != MSG_SERVICE_COMMAND) {
27169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                // Sanity check.
27269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                Log.e(TAG, "Invalid message type: " + msg.what);
27369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
27469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
27569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
27646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            // At this point it's handling onStartCommand(), with the intent passed as an Extra.
27769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (!(msg.obj instanceof Intent)) {
27869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                // Sanity check.
27969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                Log.e(TAG, "Internal error: invalid msg.obj: " + msg.obj);
28069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
28169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
28269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
28346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final Intent intent;
28446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            if (parcel instanceof Intent) {
28546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                // The real intent was passed to BugreportReceiver, which delegated to the service.
28646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                intent = (Intent) parcel;
28746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            } else {
28846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                intent = (Intent) msg.obj;
28969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
29069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final String action = intent.getAction();
29146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final int pid = intent.getIntExtra(EXTRA_PID, 0);
29246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final int max = intent.getIntExtra(EXTRA_MAX, -1);
29346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final String name = intent.getStringExtra(EXTRA_NAME);
29469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
29569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (DEBUG) Log.v(TAG, "action: " + action + ", name: " + name + ", pid: " + pid
29669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    + ", max: "+ max);
29769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            switch (action) {
29869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                case INTENT_BUGREPORT_STARTED:
29969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    if (!startProgress(name, pid, max)) {
30069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        stopSelfWhenDone();
30169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        return;
30269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    }
30346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    poll();
30469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    break;
30569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                case INTENT_BUGREPORT_FINISHED:
30646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    if (pid == 0) {
30769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
30869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        // out-of-sync dumpstate process.
30969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        Log.w(TAG, "Missing " + EXTRA_PID + " on intent " + intent);
31069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    }
31146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    onBugreportFinished(pid, intent);
31246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    break;
313bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                case INTENT_BUGREPORT_INFO_LAUNCH:
314bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    launchBugreportInfoDialog(pid);
315bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    break;
316d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                case INTENT_BUGREPORT_SCREENSHOT:
317d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    takeScreenshot(pid, true);
318d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    break;
31946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                case INTENT_BUGREPORT_SHARE:
32046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    shareBugreport(pid);
32169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    break;
3229cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                case INTENT_BUGREPORT_CANCEL:
3239cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                    cancel(pid);
3249cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                    break;
32569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                default:
32669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    Log.w(TAG, "Unsupported intent: " + action);
32769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
32869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            return;
32969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
33069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
33169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
332923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        private void poll() {
333923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            if (pollProgress()) {
334923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                // Keep polling...
335923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
33646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            } else {
33746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                Log.i(TAG, "Stopped polling");
33869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
339923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
340923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
34169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
342923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
343d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Separate thread used only to take screenshots so it doesn't block the main thread.
344d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
345d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private final class ScreenshotHandler extends Handler {
346d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public ScreenshotHandler(String name) {
347d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            super(newLooper(name));
348d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
349d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
350d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        @Override
351d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public void handleMessage(Message msg) {
352d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what != MSG_SCREENSHOT_REQUEST) {
353d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.e(TAG, "Invalid message type: " + msg.what);
354d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
355d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
356d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            handleScreenshotRequest(msg);
357d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
358d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
359d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
360d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private BugreportInfo getInfo(int pid) {
361d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = mProcesses.get(pid);
362d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
363d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.w(TAG, "Not monitoring process with PID " + pid);
364d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
365d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return info;
366d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
367d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
368d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
369923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Creates the {@link BugreportInfo} for a process and issue a system notification to
370923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * indicate its progress.
371923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     *
372923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * @return whether it succeeded or not.
373923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
374923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private boolean startProgress(String name, int pid, int max) {
375923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (name == null) {
376923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
377923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
378923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (pid == -1) {
379923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
380923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return false;
381923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
382923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (max <= 0) {
383923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
384923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return false;
38569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
38669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
387d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = new BugreportInfo(mContext, pid, name, max);
388d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (mProcesses.indexOfKey(pid) >= 0) {
389d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.w(TAG, "PID " + pid + " already watched");
390d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
391d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mProcesses.put(info.pid, info);
392923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
393d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        // Take initial screenshot.
394d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        takeScreenshot(pid, false);
395923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        updateProgress(info);
396923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        return true;
397923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
39869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
399923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
40046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Updates the system notification for a given bugreport.
401923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
402923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void updateProgress(BugreportInfo info) {
403923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (info.max <= 0 || info.progress < 0) {
404923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Invalid progress values for " + info);
405923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return;
40669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
40769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
408923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final NumberFormat nf = NumberFormat.getPercentInstance();
409923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        nf.setMinimumFractionDigits(2);
410923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        nf.setMaximumFractionDigits(2);
411923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final String percentText = nf.format((double) info.progress / info.max);
412d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Action cancelAction = new Action.Builder(null, mContext.getString(
413d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                com.android.internal.R.string.cancel), newCancelIntent(mContext, info)).build();
414d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent infoIntent = new Intent(mContext, BugreportProgressService.class);
415bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        infoIntent.setAction(INTENT_BUGREPORT_INFO_LAUNCH);
416bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        infoIntent.putExtra(EXTRA_PID, info.pid);
417bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final Action infoAction = new Action.Builder(null,
418d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                mContext.getString(R.string.bugreport_info_action),
419d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                PendingIntent.getService(mContext, info.pid, infoIntent,
420bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        PendingIntent.FLAG_UPDATE_CURRENT)).build();
421d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent screenshotIntent = new Intent(mContext, BugreportProgressService.class);
422d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        screenshotIntent.setAction(INTENT_BUGREPORT_SCREENSHOT);
423d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        screenshotIntent.putExtra(EXTRA_PID, info.pid);
424d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        PendingIntent screenshotPendingIntent = mTakingScreenshot ? null : PendingIntent
425d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .getService(mContext, info.pid, screenshotIntent,
426d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                        PendingIntent.FLAG_UPDATE_CURRENT);
427d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Action screenshotAction = new Action.Builder(null,
428d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                mContext.getString(R.string.bugreport_screenshot_action),
429d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                screenshotPendingIntent).build();
430d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
431d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final String title = mContext.getString(R.string.bugreport_in_progress_title);
432923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
433923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final String name =
434d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                info.name != null ? info.name : mContext.getString(R.string.bugreport_unnamed);
435923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
436d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Notification notification = new Notification.Builder(mContext)
437923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
438923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentTitle(title)
439923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setTicker(title)
440923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentText(name)
441923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentInfo(percentText)
442923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setProgress(info.max, info.progress, false)
443923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setOngoing(true)
444923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setLocalOnly(true)
445d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .setColor(mContext.getColor(
446923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                        com.android.internal.R.color.system_notification_accent_color))
447bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                .addAction(infoAction)
448d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .addAction(screenshotAction)
449923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .addAction(cancelAction)
450923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .build();
451923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
452d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        NotificationManager.from(mContext).notify(TAG, info.pid, notification);
453923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
454923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
455923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
45646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Creates a {@link PendingIntent} for a notification action used to cancel a bugreport.
45746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     */
45846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    private static PendingIntent newCancelIntent(Context context, BugreportInfo info) {
45946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent intent = new Intent(INTENT_BUGREPORT_CANCEL);
46046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        intent.setClass(context, BugreportProgressService.class);
46146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        intent.putExtra(EXTRA_PID, info.pid);
462bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return PendingIntent.getService(context, info.pid, intent,
463bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                PendingIntent.FLAG_UPDATE_CURRENT);
46446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    }
46546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
46646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    /**
46746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Finalizes the progress on a given bugreport and cancel its notification.
468923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
46946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    private void stopProgress(int pid) {
470d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (mProcesses.indexOfKey(pid) < 0) {
471d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.w(TAG, "PID not watched: " + pid);
472d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
473d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mProcesses.remove(pid);
47469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
475d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        stopSelfWhenDone();
476bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
477d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        NotificationManager.from(mContext).cancel(TAG, pid);
478923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
479923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
480923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
481923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Cancels a bugreport upon user's request.
482923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
483923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void cancel(int pid) {
484bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        Log.v(TAG, "cancel: pid=" + pid);
485d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(pid);
486d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info != null && !info.finished) {
487d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Cancelling bugreport service (pid=" + pid + ") on user's request");
488d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
489d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            deleteScreenshots(info);
490bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
49146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        stopProgress(pid);
492923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
4939cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme
494923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
495923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Poll {@link SystemProperties} to get the progress on each monitored process.
496923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     *
497923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * @return whether it should keep polling.
498923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
499923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private boolean pollProgress() {
500d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int total = mProcesses.size();
501d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (total == 0) {
502d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.d(TAG, "No process to poll progress.");
503d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
504d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        int activeProcesses = 0;
505d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (int i = 0; i < total; i++) {
506d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int pid = mProcesses.keyAt(i);
507d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final BugreportInfo info = mProcesses.valueAt(i);
508d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (info.finished) {
509d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (DEBUG) Log.v(TAG, "Skipping finished process " + pid);
510d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                continue;
511923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            }
512d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            activeProcesses++;
513d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final String progressKey = DUMPSTATE_PREFIX + pid + PROGRESS_SUFFIX;
514d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int progress = SystemProperties.getInt(progressKey, 0);
515d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (progress == 0) {
516d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.v(TAG, "System property " + progressKey + " is not set yet");
517d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
518d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int max = SystemProperties.getInt(DUMPSTATE_PREFIX + pid + MAX_SUFFIX, 0);
519d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final boolean maxChanged = max > 0 && max != info.max;
520d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final boolean progressChanged = progress > 0 && progress != info.progress;
521d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
522d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (progressChanged || maxChanged) {
523d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (progressChanged) {
524d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + " from "
525d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            + info.progress + " to " + progress);
526d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    info.progress = progress;
52746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                }
528d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (maxChanged) {
529d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    Log.i(TAG, "Updating max progress for PID " + pid + " from " + info.max
530d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            + " to " + max);
531d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    info.max = max;
53269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                }
533d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                info.lastUpdate = System.currentTimeMillis();
534d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                updateProgress(info);
535d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            } else {
536d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                long inactiveTime = System.currentTimeMillis() - info.lastUpdate;
537d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (inactiveTime >= INACTIVITY_TIMEOUT) {
538d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    Log.w(TAG, "No progress update for process " + pid + " since "
539d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            + info.getFormattedLastUpdate());
540d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    stopProgress(info.pid);
54169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                }
54269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
54369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
544d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (DEBUG) Log.v(TAG, "pollProgress() total=" + total + ", actives=" + activeProcesses);
545d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return activeProcesses > 0;
546923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
54769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
548923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
549bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Fetches a {@link BugreportInfo} for a given process and launches a dialog where the user can
550bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * change its values.
551bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
552bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private void launchBugreportInfoDialog(int pid) {
553bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // Copy values so it doesn't lock mProcesses while UI is being updated
554bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String name, title, description;
555d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(pid);
556d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
557d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
558d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
559d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        name = info.name;
560d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        title = info.title;
561d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        description = info.description;
562d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
563d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        collapseNotificationBar();
564d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mInfoDialog.initialize(mContext, pid, name, title, description);
565d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
566d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
567d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
568d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Starting point for taking a screenshot.
569d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
570d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * If {@code delayed} is set, it first display a toast message and waits
571d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * {@link #SCREENSHOT_DELAY_SECONDS} seconds before taking it, otherwise it takes the screenshot
572d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * right away.
573d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
574d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Typical usage is delaying when taken from the notification action, and taking it right away
575d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * upon receiving a {@link #INTENT_BUGREPORT_STARTED}.
576d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
577d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void takeScreenshot(int pid, boolean delayed) {
578d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        setTakingScreenshot(true);
579d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (delayed) {
580d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            collapseNotificationBar();
581d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final String msg = mContext.getResources()
582d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    .getQuantityString(com.android.internal.R.plurals.bugreport_countdown,
583d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            SCREENSHOT_DELAY_SECONDS, SCREENSHOT_DELAY_SECONDS);
584d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, msg);
585d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // Show a toast just once, otherwise it might be captured in the screenshot.
586d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
587d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
588d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            takeScreenshot(pid, SCREENSHOT_DELAY_SECONDS);
589d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
590d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            takeScreenshot(pid, 0);
591d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
592d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
593d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
594d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
595d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Takes a screenshot after {@code delay} seconds.
596d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
597d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void takeScreenshot(int pid, int delay) {
598d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (delay > 0) {
599d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.d(TAG, "Taking screenshot for " + pid + " in " + delay + " seconds");
600d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Message msg = mMainHandler.obtainMessage();
601d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.what = MSG_DELAYED_SCREENSHOT;
602d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.arg1 = pid;
603d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.arg2 = delay - 1;
604d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mMainHandler.sendMessageDelayed(msg, DateUtils.SECOND_IN_MILLIS);
605d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
606d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
607d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
608d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        // It's time to take the screenshot: let the proper thread handle it
609d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(pid);
610d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
611d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
612d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
613d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final String screenshotPath =
614d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                new File(mScreenshotsDir, info.getPathNextScreenshot()).getAbsolutePath();
615d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
616d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Message requestMsg = new Message();
617d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        requestMsg.what = MSG_SCREENSHOT_REQUEST;
618d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        requestMsg.arg1 = pid;
619d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        requestMsg.obj = screenshotPath;
620d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler.sendMessage(requestMsg);
621d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
622d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
623d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
624d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Sets the internal {@code mTakingScreenshot} state and updates all notifications so their
625d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * SCREENSHOT button is enabled or disabled accordingly.
626d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
627d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void setTakingScreenshot(boolean flag) {
628d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        synchronized (BugreportProgressService.this) {
629d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mTakingScreenshot = flag;
630d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            for (int i = 0; i < mProcesses.size(); i++) {
631d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                updateProgress(mProcesses.valueAt(i));
632bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
633bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
634d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
635bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
636d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void handleScreenshotRequest(Message requestMsg) {
637d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        String screenshotFile = (String) requestMsg.obj;
638d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        boolean taken = takeScreenshot(mContext, screenshotFile);
639d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        setTakingScreenshot(false);
640d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
641d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Message resultMsg = new Message();
642d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        resultMsg.what = MSG_SCREENSHOT_RESPONSE;
643d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        resultMsg.arg1 = requestMsg.arg1;
644d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        resultMsg.arg2 = taken ? 1 : 0;
645d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        resultMsg.obj = screenshotFile;
646d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler.sendMessage(resultMsg);
647d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
648bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
649d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void handleScreenshotResponse(Message resultMsg) {
650d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final boolean taken = resultMsg.arg2 != 0;
651d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(resultMsg.arg1);
652d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
653d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
654d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
655d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final File screenshotFile = new File((String) resultMsg.obj);
656d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
657d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int msgId;
658d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (taken) {
659d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.addScreenshot(screenshotFile);
660d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msgId = R.string.bugreport_screenshot_taken;
661d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
662d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // TODO: try again using Framework APIs instead of relying on screencap.
663d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msgId = R.string.bugreport_screenshot_failed;
664d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
665d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final String msg = mContext.getString(msgId);
666d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Log.d(TAG, msg);
667d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
668d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
669d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
670d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
671d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Deletes all screenshots taken for a given bugreport.
672d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
673d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void deleteScreenshots(BugreportInfo info) {
674d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (File file : info.screenshotFiles) {
675d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Deleting screenshot file " + file);
676d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            file.delete();
677d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
678bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
679bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
680bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
681923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Finishes the service when it's not monitoring any more processes.
682923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
683923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void stopSelfWhenDone() {
684d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (mProcesses.size() > 0) {
685d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (DEBUG) Log.v(TAG, "Staying alive, waiting for pids " + mProcesses);
686d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
68769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
688d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Log.v(TAG, "No more pids to handle, shutting down");
689d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        stopSelf();
690923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
69169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
692bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
693bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
694bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
695923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void onBugreportFinished(int pid, Intent intent) {
696bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        mInfoDialog.onBugreportFinished(pid);
697d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        BugreportInfo info = getInfo(pid);
698d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
699d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED first.
700d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.v(TAG, "Creating info for untracked pid " + pid);
701d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info = new BugreportInfo(mContext, pid);
702d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mProcesses.put(pid, info);
703d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
704d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.renameScreenshots(mScreenshotsDir);
705d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
706d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final File screenshot = getFileExtra(intent, EXTRA_SCREENSHOT);
707d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (screenshot != null) {
708d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.addScreenshot(screenshot);
70946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
710d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.finished = true;
71169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
712d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Configuration conf = mContext.getResources().getConfiguration();
713923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
714d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            triggerLocalNotification(mContext, info);
715b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
716b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
717b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
718b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
71969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Responsible for triggering a notification that allows the user to start a "share" intent with
72046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * the bugreport. On watches we have other methods to allow the user to start this intent
72169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * (usually by triggering it on another connected device); we don't need to display the
72269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * notification in this case.
723b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
724d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void triggerLocalNotification(final Context context, final BugreportInfo info) {
72546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
72646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
727d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Toast.makeText(context, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
728d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            stopProgress(info.pid);
729b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return;
730b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
731b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
73246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
733b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (!isPlainText) {
734b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Already zipped, send it right away.
73546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            sendBugreportNotification(context, info);
736b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
737b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Asynchronously zip the file first, then send it.
73846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            sendZippedBugreportNotification(context, info);
739b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
740b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
741b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
742b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Intent buildWarningIntent(Context context, Intent sendIntent) {
743b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Intent intent = new Intent(context, BugreportWarningActivity.class);
744b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
745b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return intent;
746b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
747b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
748b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
749b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Build {@link Intent} that can be used to share the given bugreport.
750b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
751bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static Intent buildSendIntent(Context context, BugreportInfo info) {
752bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // Files are kept on private storage, so turn into Uris that we can
753bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // grant temporary permissions for.
754bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final Uri bugreportUri = getUri(context, info.bugreportFile);
755bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
756b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
757b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final String mimeType = "application/vnd.android.bugreport";
758b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
759b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.addCategory(Intent.CATEGORY_DEFAULT);
760b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.setType(mimeType);
761b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
762bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String subject = info.title != null ? info.title : bugreportUri.getLastPathSegment();
763bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
764b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
765b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
766b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
767b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // create the ClipData object with the attachments URIs.
768d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final StringBuilder messageBody = new StringBuilder("Build info: ")
769bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append(SystemProperties.get("ro.build.description"))
770bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append("\nSerial number: ")
771bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append(SystemProperties.get("ro.serialno"));
772bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        if (!TextUtils.isEmpty(info.description)) {
773bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            messageBody.append("\nDescription: ").append(info.description);
774bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
775bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
776b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final ClipData clipData = new ClipData(null, new String[] { mimeType },
777b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                new ClipData.Item(null, null, null, bugreportUri));
778b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
779d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (File screenshot : info.screenshotFiles) {
780d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Uri screenshotUri = getUri(context, screenshot);
781b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
782b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            attachments.add(screenshotUri);
783b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
784b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.setClipData(clipData);
785b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
786b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
787b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Account sendToAccount = findSendToAccount(context);
788b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (sendToAccount != null) {
789b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
790b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
791b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
792b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return intent;
793b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
794b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
795b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
79646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Shares the bugreport upon user's request by issuing a {@link Intent#ACTION_SEND_MULTIPLE}
79746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * intent, but issuing a warning dialog the first time.
798b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
79946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    private void shareBugreport(int pid) {
800d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(pid);
801d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
802d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // Should not happen, so log if it does...
803d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.e(TAG, "INTERNAL ERROR: no info for PID " + pid + ": " + mProcesses);
804d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
80546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
806d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent sendIntent = buildSendIntent(mContext, info);
80746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent notifIntent;
808b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
809b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // Send through warning dialog by default
810d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (getWarningState(mContext, STATE_SHOW) == STATE_SHOW) {
811d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            notifIntent = buildWarningIntent(mContext, sendIntent);
812b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
813b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            notifIntent = sendIntent;
814b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
815b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
816b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
81746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        // Send the share intent...
818d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mContext.startActivity(notifIntent);
81946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
82046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        // ... and stop watching this process.
82146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        stopProgress(pid);
82246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    }
82346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
82446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    /**
82546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Sends a notitication indicating the bugreport has finished so use can share it.
82646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     */
82746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    private static void sendBugreportNotification(Context context, BugreportInfo info) {
82846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE);
82946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        shareIntent.setClass(context, BugreportProgressService.class);
83046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        shareIntent.setAction(INTENT_BUGREPORT_SHARE);
83146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        shareIntent.putExtra(EXTRA_PID, info.pid);
83246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
83369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        final String title = context.getString(R.string.bugreport_finished_title);
834b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Notification.Builder builder = new Notification.Builder(context)
835b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
83669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                .setContentTitle(title)
83769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                .setTicker(title)
838b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                .setContentText(context.getString(R.string.bugreport_finished_text))
839bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                .setContentIntent(PendingIntent.getService(context, info.pid, shareIntent,
840bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        PendingIntent.FLAG_UPDATE_CURRENT))
84146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                .setDeleteIntent(newCancelIntent(context, info))
842b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                .setLocalOnly(true)
843b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                .setColor(context.getColor(
844b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        com.android.internal.R.color.system_notification_accent_color));
845b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
846bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        if (!TextUtils.isEmpty(info.name)) {
847bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            builder.setContentInfo(info.name);
848bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
849bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
85046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        NotificationManager.from(context).notify(TAG, info.pid, builder.build());
851b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
852b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
853b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
854b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Sends a zipped bugreport notification.
855b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
856b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static void sendZippedBugreportNotification(final Context context,
85746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final BugreportInfo info) {
858b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        new AsyncTask<Void, Void, Void>() {
859b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            @Override
860b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            protected Void doInBackground(Void... params) {
86146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                info.bugreportFile = zipBugreport(info.bugreportFile);
86246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                sendBugreportNotification(context, info);
863b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                return null;
864b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
865b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }.execute();
866b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
867b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
868b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
869b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Zips a bugreport file, returning the path to the new file (or to the
870b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * original in case of failure).
871b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
872b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static File zipBugreport(File bugreportFile) {
873b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        String bugreportPath = bugreportFile.getAbsolutePath();
874b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        String zippedPath = bugreportPath.replace(".txt", ".zip");
875b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
876b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        File bugreportZippedFile = new File(zippedPath);
877b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        try (InputStream is = new FileInputStream(bugreportFile);
87869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                ZipOutputStream zos = new ZipOutputStream(
87969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
880b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            ZipEntry entry = new ZipEntry(bugreportFile.getName());
881b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            entry.setTime(bugreportFile.lastModified());
882b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            zos.putNextEntry(entry);
883b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            int totalBytes = Streams.copy(is, zos);
884b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            Log.v(TAG, "size of original bugreport: " + totalBytes + " bytes");
885b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            zos.closeEntry();
886b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Delete old file;
887b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            boolean deleted = bugreportFile.delete();
888b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            if (deleted) {
889b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
890b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            } else {
891b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
892b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
893b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return bugreportZippedFile;
894b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } catch (IOException e) {
89569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            Log.e(TAG, "exception zipping file " + zippedPath, e);
89669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            return bugreportFile; // Return original.
897b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
898b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
899b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
900b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
901b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Find the best matching {@link Account} based on build properties.
902b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
903b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Account findSendToAccount(Context context) {
904b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final AccountManager am = (AccountManager) context.getSystemService(
905b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Context.ACCOUNT_SERVICE);
906b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
907b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
908b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (!preferredDomain.startsWith("@")) {
909b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            preferredDomain = "@" + preferredDomain;
910b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
911b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
912b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Account[] accounts = am.getAccounts();
913b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        Account foundAccount = null;
914b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        for (Account account : accounts) {
915b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
916b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                if (!preferredDomain.isEmpty()) {
917b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // if we have a preferred domain and it matches, return; otherwise keep
918b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // looking
919b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    if (account.name.endsWith(preferredDomain)) {
920b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        return account;
921b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    } else {
922b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        foundAccount = account;
923b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    }
924b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // if we don't have a preferred domain, just return since it looks like
925b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // an email address
926b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                } else {
927b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    return account;
928b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                }
929b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
930b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
931b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return foundAccount;
932b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
933b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
934b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Uri getUri(Context context, File file) {
935b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
936b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
937b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
938b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static File getFileExtra(Intent intent, String key) {
939b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final String path = intent.getStringExtra(key);
940b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (path != null) {
941b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return new File(path);
942b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
943b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return null;
944b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
945b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
94669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
947bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static boolean setSystemProperty(String key, String value) {
948bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        try {
949bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (DEBUG) Log.v(TAG, "Setting system property" + key + " to " + value);
950bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            SystemProperties.set(key, value);
951bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        } catch (IllegalArgumentException e) {
952bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            Log.e(TAG, "Could not set property " + key + " to " + value, e);
953bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            return false;
954bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
955bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return true;
956bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
957bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
958bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
959bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
960bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
961bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private boolean setBugreportNameProperty(int pid, String name) {
962bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        Log.d(TAG, "Updating bugreport name to " + name);
963bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
964bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return setSystemProperty(key, name);
965bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
966bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
967bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
968bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Updates the user-provided details of a bugreport.
969bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
970bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private void updateBugreportInfo(int pid, String name, String title, String description) {
971d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(pid);
972d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
973d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
974d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
975d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.title = title;
976d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.description = description;
977d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (name != null && !info.name.equals(name)) {
978d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.name = name;
979d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            updateProgress(info);
980d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
981d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
982d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
983d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void collapseNotificationBar() {
984d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
985d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
986d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
987d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static Looper newLooper(String name) {
988d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final HandlerThread thread = new HandlerThread(name, THREAD_PRIORITY_BACKGROUND);
989d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        thread.start();
990d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return thread.getLooper();
991d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
992d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
993d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
994d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Takes a screenshot and save it to the given location.
995d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
996d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static boolean takeScreenshot(Context context, String screenshotFile) {
997d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE))
998d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .vibrate(150);
999d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final ProcessBuilder screencap = new ProcessBuilder()
1000d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .command("/system/bin/screencap", "-p", screenshotFile);
1001d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Log.d(TAG, "Taking screenshot using " + screencap.command());
1002d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        try {
1003d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int exitValue = screencap.start().waitFor();
1004d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (exitValue == 0) {
1005d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return true;
1006bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1007d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.e(TAG, "screencap (" + screencap.command() + ") failed: " + exitValue);
1008d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } catch (IOException e) {
1009d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.e(TAG, "screencap (" + screencap.command() + ") failed", e);
1010d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } catch (InterruptedException e) {
1011d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.w(TAG, "Thread interrupted while screencap still running");
1012d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Thread.currentThread().interrupt();
1013bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1014d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return false;
1015bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1016bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1017bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1018bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Checks whether a character is valid on bugreport names.
1019bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1020bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    @VisibleForTesting
1021bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static boolean isValid(char c) {
1022bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
1023bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                || c == '_' || c == '-';
1024bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1025bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1026bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1027bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Helper class encapsulating the UI elements and logic used to display a dialog where user
1028bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * can change the details of a bugreport.
1029bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1030bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private final class BugreportInfoDialog {
1031bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoName;
1032bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoTitle;
1033bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoDescription;
1034bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private AlertDialog mDialog;
1035bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private Button mOkButton;
1036bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private int mPid;
1037bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1038bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1039bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Last "committed" value of the bugreport name.
1040bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1041bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Once initially set, it's only updated when user clicks the OK button.
1042bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1043bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private String mSavedName;
1044bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1045bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1046bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Last value of the bugreport name as entered by the user.
1047bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1048bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Every time it's changed the equivalent system property is changed as well, but if the
1049bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
1050bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1051bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
1052bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * user changed the name but didn't clicked OK yet (for example, because the user is typing
1053bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * the description). The only drawback is that if the user changes the name while
1054bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
1055bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
1056bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
1057bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * such drawback.
1058bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1059bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private String mTempName;
1060bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1061bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1062bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Sets its internal state and displays the dialog.
1063bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1064d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        private void initialize(Context context, int pid, String name, String title,
1065bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                String description) {
1066bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // First initializes singleton.
1067bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mDialog == null) {
1068bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                @SuppressLint("InflateParams")
1069bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                // It's ok pass null ViewRoot on AlertDialogs.
1070bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                final View view = View.inflate(context, R.layout.dialog_bugreport_info, null);
1071bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1072bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName = (EditText) view.findViewById(R.id.name);
1073bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoTitle = (EditText) view.findViewById(R.id.title);
1074bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoDescription = (EditText) view.findViewById(R.id.description);
1075bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1076bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
1077bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1078bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    @Override
1079bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    public void onFocusChange(View v, boolean hasFocus) {
1080bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        if (hasFocus) {
1081bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                            return;
1082bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        }
1083bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        sanitizeName();
1084bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    }
1085bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                });
1086bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1087bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mDialog = new AlertDialog.Builder(context)
1088bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setView(view)
1089bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setTitle(context.getString(R.string.bugreport_info_dialog_title))
1090bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setCancelable(false)
1091bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setPositiveButton(context.getString(com.android.internal.R.string.ok),
1092bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                null)
1093bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setNegativeButton(context.getString(com.android.internal.R.string.cancel),
1094bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                new DialogInterface.OnClickListener()
1095bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                {
1096bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    @Override
1097bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    public void onClick(DialogInterface dialog, int id)
1098bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    {
1099bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                        if (!mTempName.equals(mSavedName)) {
1100bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            // Must restore dumpstate's name since it was changed
1101bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            // before user clicked OK.
1102bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            setBugreportNameProperty(mPid, mSavedName);
1103bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                        }
1104bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    }
1105bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                })
1106bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .create();
1107bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1108bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mDialog.getWindow().setAttributes(
1109bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        new WindowManager.LayoutParams(
1110bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG));
1111bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1112bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1113bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1114bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // Then set fields.
1115bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mSavedName = mTempName = name;
1116bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mPid = pid;
1117bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (!TextUtils.isEmpty(name)) {
1118bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(name);
1119bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1120bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (!TextUtils.isEmpty(title)) {
1121bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoTitle.setText(title);
1122bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1123bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (!TextUtils.isEmpty(description)) {
1124bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoDescription.setText(description);
1125bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1126bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1127bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // And finally display it.
1128bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mDialog.show();
1129bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1130bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // TODO: in a traditional AlertDialog, when the positive button is clicked the
1131bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // dialog is always closed, but we need to validate the name first, so we need to
1132bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // get a reference to it, which is only available after it's displayed.
1133bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // It would be cleaner to use a regular dialog instead, but let's keep this
1134bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // workaround for now and change it later, when we add another button to take
1135bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // extra screenshots.
1136bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mOkButton == null) {
1137bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mOkButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
1138bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mOkButton.setOnClickListener(new View.OnClickListener() {
1139bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1140bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    @Override
1141bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    public void onClick(View view) {
1142bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        sanitizeName();
1143bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String name = mInfoName.getText().toString();
1144bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String title = mInfoTitle.getText().toString();
1145bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String description = mInfoDescription.getText().toString();
1146bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1147bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        updateBugreportInfo(mPid, name, title, description);
1148bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        mDialog.dismiss();
1149bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    }
1150bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                });
1151bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1152bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1153bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1154bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1155bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Sanitizes the user-provided value for the {@code name} field, automatically replacing
1156bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * invalid characters if necessary.
1157bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1158d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        private void sanitizeName() {
1159bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            String name = mInfoName.getText().toString();
1160bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (name.equals(mTempName)) {
1161bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
1162bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                return;
1163bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1164bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            final StringBuilder safeName = new StringBuilder(name.length());
1165bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            boolean changed = false;
1166bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            for (int i = 0; i < name.length(); i++) {
1167bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                final char c = name.charAt(i);
1168bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                if (isValid(c)) {
1169bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    safeName.append(c);
1170bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                } else {
1171bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    changed = true;
1172bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    safeName.append('_');
1173bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                }
1174bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1175bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (changed) {
1176bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                Log.v(TAG, "changed invalid name '" + name + "' to '" + safeName + "'");
1177bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                name = safeName.toString();
1178bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(name);
1179bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1180bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mTempName = name;
1181bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1182bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // Must update system property for the cases where dumpstate finishes
1183bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // while the user is still entering other fields (like title or
1184bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // description)
1185bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            setBugreportNameProperty(mPid, name);
1186bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1187bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1188bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme       /**
1189bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Notifies the dialog that the bugreport has finished so it disables the {@code name}
1190bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * field.
1191bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>Once the bugreport is finished dumpstate has already generated the final files, so
1192bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * changing the name would have no effect.
1193bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1194d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        private void onBugreportFinished(int pid) {
1195bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mInfoName != null) {
1196bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setEnabled(false);
1197bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(mSavedName);
1198bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1199bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1200bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1201bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1202bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
120369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /**
120446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Information about a bugreport process while its in progress.
120569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     */
120669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final class BugreportInfo {
1207719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        private final Context context;
1208719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme
120969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
121046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * {@code pid} of the {@code dumpstate} process generating the bugreport.
121169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
121269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        final int pid;
121369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
121469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
121546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Name of the bugreport, will be used to rename the final files.
121669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * <p>
121746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Initial value is the bugreport filename reported by {@code dumpstate}, but user can
121869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * change it later to a more meaningful name.
121969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
1220719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        String name;
122169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
122269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
1223bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * User-provided, one-line summary of the bug; when set, will be used as the subject
1224bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
1225bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1226bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        String title;
1227bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1228bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1229bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * User-provided, detailed description of the bugreport; when set, will be added to the body
1230bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
1231bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1232bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        String description;
1233bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1234bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
123546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Maximum progress of the bugreport generation.
123669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
1237719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        int max;
123869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
123969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
124046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Current progress of the bugreport generation.
124169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
124269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        int progress;
124369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
124469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
124569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * Time of the last progress update.
124669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
124769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        long lastUpdate = System.currentTimeMillis();
124869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
124946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
125046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Path of the main bugreport file.
125146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
125246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        File bugreportFile;
125346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
125446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
1255d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Path of the screenshot files.
125646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1257d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        List<File> screenshotFiles = new ArrayList<>(1);
125846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
125946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
126046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Whether dumpstate sent an intent informing it has finished.
126146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
126246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        boolean finished;
126346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
126446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
1265d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Internal counter used to name screenshot files.
1266d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1267d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        int screenshotCounter;
1268d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1269d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
127046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
127146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1272719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        BugreportInfo(Context context, int pid, String name, int max) {
1273719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme            this.context = context;
127469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.pid = pid;
127569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.name = name;
127669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.max = max;
127769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
127869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
127946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
128046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
128146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * without a previous call to BUGREPORT_STARTED.
128246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
128346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        BugreportInfo(Context context, int pid) {
128446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            this(context, pid, null, 0);
128546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            this.finished = true;
128646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
128746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
1288d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1289d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Gets the name for next screenshot file.
1290d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1291d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        String getPathNextScreenshot() {
1292d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotCounter ++;
1293d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return "screenshot-" + pid + "-" + screenshotCounter + ".png";
1294d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1295d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1296d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1297d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Saves the location of a taken screenshot so it can be sent out at the end.
1298d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1299d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        void addScreenshot(File screenshot) {
1300d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotFiles.add(screenshot);
1301d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1302d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1303d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1304d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Rename all screenshots files so that they contain the user-generated name instead of pid.
1305d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1306d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        void renameScreenshots(File screenshotDir) {
1307d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (TextUtils.isEmpty(name)) {
1308d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
1309d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
1310d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
1311d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            for (File oldFile : screenshotFiles) {
1312d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final String oldName = oldFile.getName();
1313d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final String newName = oldName.replace(Integer.toString(pid), name);
1314d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final File newFile;
1315d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (!newName.equals(oldName)) {
1316d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    final File renamedFile = new File(screenshotDir, newName);
1317d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
1318d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                } else {
1319d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
1320d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    newFile = oldFile;
1321d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                }
1322d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                renamedFiles.add(newFile);
1323d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
1324d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotFiles = renamedFiles;
1325d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1326d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
132769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        String getFormattedLastUpdate() {
1328719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme            return DateUtils.formatDateTime(context, lastUpdate,
1329719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
133069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
133169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
133269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        @Override
133369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        public String toString() {
133469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final float percent = ((float) progress * 100 / max);
133546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            return "pid: " + pid + ", name: " + name + ", finished: " + finished
1336bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    + "\n\ttitle: " + title + "\n\tdescription: " + description
1337d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    + "\n\tfile: " + bugreportFile + "\n\tscreenshots: " + screenshotFiles
133846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    + "\n\tprogress: " + progress + "/" + max + "(" + percent + ")"
133946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    + "\n\tlast_update: " + getFormattedLastUpdate();
134069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
134169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
1342b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme}
1343