BugreportProgressService.java revision 208b1881ae924cd0c2bed326555e4aa18424d927
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;
244967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Lemeimport java.io.ByteArrayInputStream;
25b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.File;
2669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.io.FileDescriptor;
27b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.FileInputStream;
28b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.FileOutputStream;
29b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.IOException;
30b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.InputStream;
3169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.io.PrintWriter;
324967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Lemeimport java.nio.charset.StandardCharsets;
3369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.text.NumberFormat;
34b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.ArrayList;
354967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Lemeimport java.util.Enumeration;
36d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport java.util.List;
37b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.zip.ZipEntry;
384967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Lemeimport java.util.zip.ZipFile;
39b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.zip.ZipOutputStream;
40b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
41b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport libcore.io.Streams;
42b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
43bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport com.android.internal.annotations.VisibleForTesting;
446605bd89c53494b59717a826f9a17641bc32da41Felipe Lemeimport com.android.internal.logging.MetricsLogger;
456605bd89c53494b59717a826f9a17641bc32da41Felipe Lemeimport com.android.internal.logging.MetricsProto.MetricsEvent;
46b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport com.google.android.collect.Lists;
47b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
48b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.accounts.Account;
49b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.accounts.AccountManager;
50bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.annotation.SuppressLint;
51bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.app.AlertDialog;
52b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.Notification;
539cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Lemeimport android.app.Notification.Action;
54b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.NotificationManager;
55b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.PendingIntent;
56b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.Service;
57b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.ClipData;
58b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.Context;
59bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.content.DialogInterface;
60b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.Intent;
61b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.res.Configuration;
62b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.net.Uri;
63b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.AsyncTask;
6469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Handler;
6569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.HandlerThread;
66b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.IBinder;
6769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Looper;
6869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Message;
69c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Lemeimport android.os.Parcel;
7069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Parcelable;
71b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.SystemProperties;
72d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport android.os.Vibrator;
73b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.support.v4.content.FileProvider;
74bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.text.TextUtils;
7569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.text.format.DateUtils;
76b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.util.Log;
77b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.util.Patterns;
7869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.util.SparseArray;
79bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.View;
80bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.WindowManager;
81bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.View.OnFocusChangeListener;
82bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.inputmethod.EditorInfo;
83bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.widget.Button;
84bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.widget.EditText;
85b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.widget.Toast;
86b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
8769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme/**
8846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme * Service used to keep progress of bugreport processes ({@code dumpstate}).
8969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <p>
9069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * The workflow is:
9169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
92fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with a sequential id,
93fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme * its pid, and the estimated total effort.
9469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
9569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Upon start, this service:
9669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
9769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
9869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
9969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>If the progress changed, it updates the system notification.
10069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
10169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>As {@code dumpstate} progresses, it updates the system property.
10269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
10369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
10469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * turn:
10569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
10646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme * <li>Updates the system notification so user can share the bugreport.
10769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Stops monitoring that {@code dumpstate} process.
10869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Stops itself if it doesn't have any process left to monitor.
10969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
11069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
11169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme */
112b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemepublic class BugreportProgressService extends Service {
113c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    private static final String TAG = "BugreportProgressService";
11469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final boolean DEBUG = false;
115b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
116b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static final String AUTHORITY = "com.android.shell";
117b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
11846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    // External intents sent by dumpstate.
11969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
12069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";
121226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski    static final String INTENT_REMOTE_BUGREPORT_FINISHED =
122226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski            "android.intent.action.REMOTE_BUGREPORT_FINISHED";
12346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
12446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    // Internal intents used on notification actions.
1259cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme    static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
12646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
127bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String INTENT_BUGREPORT_INFO_LAUNCH =
128bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            "android.intent.action.BUGREPORT_INFO_LAUNCH";
129d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    static final String INTENT_BUGREPORT_SCREENSHOT =
130d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            "android.intent.action.BUGREPORT_SCREENSHOT";
13169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
132b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
133b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
134fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    static final String EXTRA_ID = "android.intent.extra.ID";
13569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_PID = "android.intent.extra.PID";
13669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_MAX = "android.intent.extra.MAX";
13769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_NAME = "android.intent.extra.NAME";
138bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String EXTRA_TITLE = "android.intent.extra.TITLE";
139bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
14069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
141c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    static final String EXTRA_INFO = "android.intent.extra.INFO";
14269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
14369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final int MSG_SERVICE_COMMAND = 1;
14469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final int MSG_POLL = 2;
145d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_DELAYED_SCREENSHOT = 3;
146d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_SCREENSHOT_REQUEST = 4;
147d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_SCREENSHOT_RESPONSE = 5;
148d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1498648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme    // Passed to Message.obtain() when msg.arg2 is not used.
1508648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme    private static final int UNUSED_ARG2 = -2;
1518648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme
152d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
153d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Delay before a screenshot is taken.
154d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
155d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Should be at least 3 seconds, otherwise its toast might show up in the screenshot.
156d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
157d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    static final int SCREENSHOT_DELAY_SECONDS = 3;
15869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
15969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** Polling frequency, in milliseconds. */
160bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final long POLLING_FREQUENCY = 2 * DateUtils.SECOND_IN_MILLIS;
16169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
16269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
1631eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme    private static final long INACTIVITY_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
16469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
165719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    /** System properties used for monitoring progress. */
166719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String DUMPSTATE_PREFIX = "dumpstate.";
167719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String PROGRESS_SUFFIX = ".progress";
168719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String MAX_SUFFIX = ".max";
169bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static final String NAME_SUFFIX = ".name";
1709cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme
171bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /** System property (and value) used to stop dumpstate. */
172d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    // TODO: should call ActiveManager API instead
173719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String CTL_STOP = "ctl.stop";
1744cc863338d5e43b6189e05498d7cb53ebba135e1Felipe Leme    private static final String BUGREPORT_SERVICE = "bugreportplus";
17569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
176d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
177d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Directory on Shell's data storage where screenshots will be stored.
178d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
179d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Must be a path supported by its FileProvider.
180d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
181d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final String SCREENSHOT_DIR = "bugreports";
182d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
183fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    /** Managed dumpstate processes (keyed by id) */
18469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>();
18569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
186d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private Context mContext;
187d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private ServiceHandler mMainHandler;
188d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private ScreenshotHandler mScreenshotHandler;
18969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
190bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog();
191bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
192d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private File mScreenshotsDir;
193d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
194d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
195d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Flag indicating whether a screenshot is being taken.
196d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
197d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * This is the only state that is shared between the 2 handlers and hence must have synchronized
198d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * access.
199d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
200d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private boolean mTakingScreenshot;
201d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
20269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
20369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void onCreate() {
204d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mContext = getApplicationContext();
205d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler = new ServiceHandler("BugreportProgressServiceMainThread");
206d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
207d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
2084f663f6c25488a83a438a01dfef8372aa4e6b2aaFelipe Leme        mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR);
209d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (!mScreenshotsDir.exists()) {
210d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots");
211d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (!mScreenshotsDir.mkdir()) {
212d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.w(TAG, "Could not create directory " + mScreenshotsDir);
213d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
214d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
21569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
216b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
217b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    @Override
218b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    public int onStartCommand(Intent intent, int flags, int startId) {
219b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (intent != null) {
22069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            // Handle it in a separate thread.
221d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Message msg = mMainHandler.obtainMessage();
22269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            msg.what = MSG_SERVICE_COMMAND;
22369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            msg.obj = intent;
224d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mMainHandler.sendMessage(msg);
225b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
22669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
22769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        // If service is killed it cannot be recreated because it would not know which
228fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        // dumpstate IDs it would have to watch.
229b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return START_NOT_STICKY;
230b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
231b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
232b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    @Override
233b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    public IBinder onBind(Intent intent) {
234b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return null;
235b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
236b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
23769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
23869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void onDestroy() {
239d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler.getLooper().quit();
240d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler.getLooper().quit();
24169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        super.onDestroy();
24269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
243b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
24469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
24569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
246d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int size = mProcesses.size();
247d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (size == 0) {
248d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            writer.printf("No monitored processes");
249d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
250d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
251d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        writer.printf("Monitored dumpstate processes\n");
252d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        writer.printf("-----------------------------\n");
253d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (int i = 0; i < size; i++) {
254d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            writer.printf("%s\n", mProcesses.valueAt(i));
25569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
25669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
25769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
258d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
259d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Main thread used to handle all requests but taking screenshots.
260d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
26169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private final class ServiceHandler extends Handler {
262d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public ServiceHandler(String name) {
263d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            super(newLooper(name));
26469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
26569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
26669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        @Override
26769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        public void handleMessage(Message msg) {
26869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (msg.what == MSG_POLL) {
269923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                poll();
27069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
27169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
27269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
273d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what == MSG_DELAYED_SCREENSHOT) {
274d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                takeScreenshot(msg.arg1, msg.arg2);
275d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
276d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
277d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
278d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what == MSG_SCREENSHOT_RESPONSE) {
279d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                handleScreenshotResponse(msg);
280d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
281d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
282d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
28369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (msg.what != MSG_SERVICE_COMMAND) {
28469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                // Sanity check.
28569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                Log.e(TAG, "Invalid message type: " + msg.what);
28669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
28769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
28869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
28946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            // At this point it's handling onStartCommand(), with the intent passed as an Extra.
29069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (!(msg.obj instanceof Intent)) {
29169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                // Sanity check.
292af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                Log.wtf(TAG, "handleMessage(): invalid msg.obj type: " + msg.obj);
29369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
29469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
29569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
29646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final Intent intent;
29746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            if (parcel instanceof Intent) {
29846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                // The real intent was passed to BugreportReceiver, which delegated to the service.
29946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                intent = (Intent) parcel;
30046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            } else {
30146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                intent = (Intent) msg.obj;
30269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
30369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final String action = intent.getAction();
30446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final int pid = intent.getIntExtra(EXTRA_PID, 0);
30585ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            final int id = intent.getIntExtra(EXTRA_ID, 0);
30646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final int max = intent.getIntExtra(EXTRA_MAX, -1);
30746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final String name = intent.getStringExtra(EXTRA_NAME);
30869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
309fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (DEBUG)
310fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id + ", pid: "
311fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        + pid + ", max: " + max);
31269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            switch (action) {
31369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                case INTENT_BUGREPORT_STARTED:
314fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    if (!startProgress(name, id, pid, max)) {
31569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        stopSelfWhenDone();
31669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        return;
31769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    }
31846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    poll();
31969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    break;
32069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                case INTENT_BUGREPORT_FINISHED:
321fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    if (id == 0) {
32269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
32369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        // out-of-sync dumpstate process.
324fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        Log.w(TAG, "Missing " + EXTRA_ID + " on intent " + intent);
32569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    }
326fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    onBugreportFinished(id, intent);
32746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    break;
328bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                case INTENT_BUGREPORT_INFO_LAUNCH:
329fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    launchBugreportInfoDialog(id);
330bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    break;
331d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                case INTENT_BUGREPORT_SCREENSHOT:
332fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    takeScreenshot(id, true);
333d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    break;
33446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                case INTENT_BUGREPORT_SHARE:
335fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    shareBugreport(id, (BugreportInfo) intent.getParcelableExtra(EXTRA_INFO));
33669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    break;
3379cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                case INTENT_BUGREPORT_CANCEL:
338fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    cancel(id);
3399cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                    break;
34069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                default:
34169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    Log.w(TAG, "Unsupported intent: " + action);
34269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
34369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            return;
34469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
34569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
34669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
347923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        private void poll() {
348923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            if (pollProgress()) {
349923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                // Keep polling...
350923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
35146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            } else {
35246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                Log.i(TAG, "Stopped polling");
35369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
354923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
355923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
35669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
357923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
358d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Separate thread used only to take screenshots so it doesn't block the main thread.
359d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
360d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private final class ScreenshotHandler extends Handler {
361d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public ScreenshotHandler(String name) {
362d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            super(newLooper(name));
363d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
364d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
365d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        @Override
366d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public void handleMessage(Message msg) {
367d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what != MSG_SCREENSHOT_REQUEST) {
368d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.e(TAG, "Invalid message type: " + msg.what);
369d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
370d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
371d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            handleScreenshotRequest(msg);
372d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
373d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
374d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
375fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private BugreportInfo getInfo(int id) {
376fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = mProcesses.get(id);
377d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
378fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "Not monitoring process with ID " + id);
379d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
380d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return info;
381d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
382d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
383d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
384923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Creates the {@link BugreportInfo} for a process and issue a system notification to
385923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * indicate its progress.
386923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     *
387923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * @return whether it succeeded or not.
388923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
389fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private boolean startProgress(String name, int id, int pid, int max) {
390923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (name == null) {
391923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
392923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
393fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (id == -1) {
394fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.e(TAG, "Missing " + EXTRA_ID + " on start intent");
395fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            return false;
396fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        }
397923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (pid == -1) {
398923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
399923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return false;
400923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
401923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (max <= 0) {
402923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
403923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return false;
40469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
40569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
406fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = new BugreportInfo(mContext, id, pid, name, max);
407fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (mProcesses.indexOfKey(id) >= 0) {
408fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "ID " + id + " already watched");
409d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
410fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mProcesses.put(info.id, info);
411923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
412d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        // Take initial screenshot.
413fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        takeScreenshot(id, false);
414923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        updateProgress(info);
415923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        return true;
416923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
41769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
418923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
41946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Updates the system notification for a given bugreport.
420923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
421923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void updateProgress(BugreportInfo info) {
422923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (info.max <= 0 || info.progress < 0) {
423923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Invalid progress values for " + info);
424923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return;
42569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
42669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
427923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final NumberFormat nf = NumberFormat.getPercentInstance();
428923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        nf.setMinimumFractionDigits(2);
429923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        nf.setMaximumFractionDigits(2);
430923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final String percentText = nf.format((double) info.progress / info.max);
431d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Action cancelAction = new Action.Builder(null, mContext.getString(
432d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                com.android.internal.R.string.cancel), newCancelIntent(mContext, info)).build();
433d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent infoIntent = new Intent(mContext, BugreportProgressService.class);
434bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        infoIntent.setAction(INTENT_BUGREPORT_INFO_LAUNCH);
435fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        infoIntent.putExtra(EXTRA_ID, info.id);
436db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme        final PendingIntent infoPendingIntent =
437db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme                PendingIntent.getService(mContext, info.id, infoIntent,
438db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme                PendingIntent.FLAG_UPDATE_CURRENT);
439bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final Action infoAction = new Action.Builder(null,
440d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                mContext.getString(R.string.bugreport_info_action),
441db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme                infoPendingIntent).build();
442d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent screenshotIntent = new Intent(mContext, BugreportProgressService.class);
443d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        screenshotIntent.setAction(INTENT_BUGREPORT_SCREENSHOT);
444fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        screenshotIntent.putExtra(EXTRA_ID, info.id);
445d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        PendingIntent screenshotPendingIntent = mTakingScreenshot ? null : PendingIntent
446fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                .getService(mContext, info.id, screenshotIntent,
447d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                        PendingIntent.FLAG_UPDATE_CURRENT);
448d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Action screenshotAction = new Action.Builder(null,
449d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                mContext.getString(R.string.bugreport_screenshot_action),
450d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                screenshotPendingIntent).build();
451d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
452fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final String title = mContext.getString(R.string.bugreport_in_progress_title, info.id);
453923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
454923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final String name =
455d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                info.name != null ? info.name : mContext.getString(R.string.bugreport_unnamed);
456923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
457208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        final Notification notification = newBaseNotification(mContext)
458923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentTitle(title)
459923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setTicker(title)
460923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentText(name)
461923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentInfo(percentText)
462923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setProgress(info.max, info.progress, false)
463923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setOngoing(true)
464db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme                .setContentIntent(infoPendingIntent)
465208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme                .setActions(infoAction, screenshotAction, cancelAction)
466923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .build();
467923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
4682288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme        if (info.finished) {
4692288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme            Log.w(TAG, "Not sending progress notification because bugreport has finished already ("
4702288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                    + info + ")");
4712288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme            return;
4722288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme        }
473262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme        if (DEBUG) {
474262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme            Log.d(TAG, "Sending 'Progress' notification for id " + info.id + "(pid " + info.pid
475262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                    + "): " + percentText);
476262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme        }
477fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        NotificationManager.from(mContext).notify(TAG, info.id, notification);
478923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
479923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
480923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
48146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Creates a {@link PendingIntent} for a notification action used to cancel a bugreport.
48246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     */
48346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    private static PendingIntent newCancelIntent(Context context, BugreportInfo info) {
48446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent intent = new Intent(INTENT_BUGREPORT_CANCEL);
48546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        intent.setClass(context, BugreportProgressService.class);
486fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        intent.putExtra(EXTRA_ID, info.id);
487fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        return PendingIntent.getService(context, info.id, intent,
488bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                PendingIntent.FLAG_UPDATE_CURRENT);
48946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    }
49046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
49146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    /**
49246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Finalizes the progress on a given bugreport and cancel its notification.
493923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
494fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void stopProgress(int id) {
495fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (mProcesses.indexOfKey(id) < 0) {
496fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "ID not watched: " + id);
497d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
498fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "Removing ID " + id);
499fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mProcesses.remove(id);
50069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
501fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "stopProgress(" + id + "): cancel notification");
502fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        NotificationManager.from(mContext).cancel(TAG, id);
5030f2daaf2f7f0f9a35512e452231fd34e743ddc51Felipe Leme        stopSelfWhenDone();
504923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
505923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
506923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
507923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Cancels a bugreport upon user's request.
508923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
509fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void cancel(int id) {
5106605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_CANCEL);
511fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "cancel: ID=" + id);
512fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
513d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info != null && !info.finished) {
514fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
515d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
516d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            deleteScreenshots(info);
517bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
518fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        stopProgress(id);
519923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
5209cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme
521923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
522923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Poll {@link SystemProperties} to get the progress on each monitored process.
523923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     *
524923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * @return whether it should keep polling.
525923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
526923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private boolean pollProgress() {
527d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int total = mProcesses.size();
528d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (total == 0) {
529d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.d(TAG, "No process to poll progress.");
530d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
531d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        int activeProcesses = 0;
532d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (int i = 0; i < total; i++) {
533d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final BugreportInfo info = mProcesses.valueAt(i);
534af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            if (info == null) {
535fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                Log.wtf(TAG, "pollProgress(): null info at index " + i + "(ID = "
536af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                        + mProcesses.keyAt(i) + ")");
537af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                continue;
538af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            }
539af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme
540af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            final int pid = info.pid;
541fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            final int id = info.id;
542d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (info.finished) {
54385ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme                if (DEBUG) Log.v(TAG, "Skipping finished process " + pid + " (id: " + id + ")");
544d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                continue;
545923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            }
546d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            activeProcesses++;
547d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final String progressKey = DUMPSTATE_PREFIX + pid + PROGRESS_SUFFIX;
548d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int progress = SystemProperties.getInt(progressKey, 0);
549d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (progress == 0) {
550d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.v(TAG, "System property " + progressKey + " is not set yet");
551d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
552d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int max = SystemProperties.getInt(DUMPSTATE_PREFIX + pid + MAX_SUFFIX, 0);
553d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final boolean maxChanged = max > 0 && max != info.max;
554d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final boolean progressChanged = progress > 0 && progress != info.progress;
555d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
556d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (progressChanged || maxChanged) {
557d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (progressChanged) {
558fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + "(id: " + id
559fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                            + ") from " + info.progress + " to " + progress);
560d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    info.progress = progress;
56146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                }
562d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (maxChanged) {
563fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    Log.i(TAG, "Updating max progress for PID " + pid + "(id: " + id
564fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                            + ") from " + info.max + " to " + max);
565d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    info.max = max;
56669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                }
567d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                info.lastUpdate = System.currentTimeMillis();
568d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                updateProgress(info);
569d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            } else {
570d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                long inactiveTime = System.currentTimeMillis() - info.lastUpdate;
571d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (inactiveTime >= INACTIVITY_TIMEOUT) {
572fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    Log.w(TAG, "No progress update for PID " + pid + " since "
573d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            + info.getFormattedLastUpdate());
574fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    stopProgress(info.id);
57569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                }
57669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
57769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
578d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (DEBUG) Log.v(TAG, "pollProgress() total=" + total + ", actives=" + activeProcesses);
579d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return activeProcesses > 0;
580923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
58169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
582923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
583bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Fetches a {@link BugreportInfo} for a given process and launches a dialog where the user can
584bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * change its values.
585bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
586fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void launchBugreportInfoDialog(int id) {
5876605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_DETAILS);
588bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // Copy values so it doesn't lock mProcesses while UI is being updated
589bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String name, title, description;
590fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
591d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
5921eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // Most likely am killed Shell before user tapped the notification. Since system might
5931eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // be too busy anwyays, it's better to ignore the notification and switch back to the
5941eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // non-interactive mode (where the bugerport will be shared upon completion).
595bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme            Log.w(TAG, "launchBugreportInfoDialog(): canceling notification because id " + id
596bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                    + " was not found");
5971eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // TODO: add test case to make sure notification is canceled.
5981eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            NotificationManager.from(mContext).cancel(TAG, id);
599d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
600d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
601d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
602d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        collapseNotificationBar();
603fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        mInfoDialog.initialize(mContext, info);
604d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
605d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
606d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
607d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Starting point for taking a screenshot.
608d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
609d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * If {@code delayed} is set, it first display a toast message and waits
610d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * {@link #SCREENSHOT_DELAY_SECONDS} seconds before taking it, otherwise it takes the screenshot
611d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * right away.
612d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
613d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Typical usage is delaying when taken from the notification action, and taking it right away
614d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * upon receiving a {@link #INTENT_BUGREPORT_STARTED}.
615d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
616fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void takeScreenshot(int id, boolean delayed) {
61752ca701cd4e1363d562b0f418d45b3420e207a6bFelipe Leme        if (delayed) {
61852ca701cd4e1363d562b0f418d45b3420e207a6bFelipe Leme            // Only logs screenshots requested from the notification action.
61952ca701cd4e1363d562b0f418d45b3420e207a6bFelipe Leme            MetricsLogger.action(this,
62052ca701cd4e1363d562b0f418d45b3420e207a6bFelipe Leme                    MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
62152ca701cd4e1363d562b0f418d45b3420e207a6bFelipe Leme        }
6221eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        if (getInfo(id) == null) {
6231eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // Most likely am killed Shell before user tapped the notification. Since system might
6241eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // be too busy anwyays, it's better to ignore the notification and switch back to the
6251eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // non-interactive mode (where the bugerport will be shared upon completion).
626bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme            Log.w(TAG, "takeScreenshot(): canceling notification because id " + id
627bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                    + " was not found");
6281eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // TODO: add test case to make sure notification is canceled.
6291eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            NotificationManager.from(mContext).cancel(TAG, id);
6301eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            return;
6311eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        }
632d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        setTakingScreenshot(true);
633d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (delayed) {
634d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            collapseNotificationBar();
635d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final String msg = mContext.getResources()
636d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    .getQuantityString(com.android.internal.R.plurals.bugreport_countdown,
637d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            SCREENSHOT_DELAY_SECONDS, SCREENSHOT_DELAY_SECONDS);
638d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, msg);
639d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // Show a toast just once, otherwise it might be captured in the screenshot.
640d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
641d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
642fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            takeScreenshot(id, SCREENSHOT_DELAY_SECONDS);
643d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
644fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            takeScreenshot(id, 0);
645d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
646d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
647d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
648d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
649d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Takes a screenshot after {@code delay} seconds.
650d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
651fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void takeScreenshot(int id, int delay) {
652d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (delay > 0) {
653fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "Taking screenshot for " + id + " in " + delay + " seconds");
654d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Message msg = mMainHandler.obtainMessage();
655d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.what = MSG_DELAYED_SCREENSHOT;
656fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            msg.arg1 = id;
657d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.arg2 = delay - 1;
658d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mMainHandler.sendMessageDelayed(msg, DateUtils.SECOND_IN_MILLIS);
659d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
660d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
661d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
662d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        // It's time to take the screenshot: let the proper thread handle it
663fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
664d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
665d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
666d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
667d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final String screenshotPath =
668d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                new File(mScreenshotsDir, info.getPathNextScreenshot()).getAbsolutePath();
669d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
6708648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme        Message.obtain(mScreenshotHandler, MSG_SCREENSHOT_REQUEST, id, UNUSED_ARG2, screenshotPath)
6718648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme                .sendToTarget();
672d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
673d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
674d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
675d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Sets the internal {@code mTakingScreenshot} state and updates all notifications so their
676d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * SCREENSHOT button is enabled or disabled accordingly.
677d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
678d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void setTakingScreenshot(boolean flag) {
679d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        synchronized (BugreportProgressService.this) {
680d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mTakingScreenshot = flag;
681d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            for (int i = 0; i < mProcesses.size(); i++) {
6822288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                final BugreportInfo info = mProcesses.valueAt(i);
6832288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                if (info.finished) {
6842288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                    Log.d(TAG, "Not updating progress because share notification was already sent");
6852288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                    continue;
6862288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                }
6872288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                updateProgress(info);
688bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
689bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
690d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
691bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
692d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void handleScreenshotRequest(Message requestMsg) {
693d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        String screenshotFile = (String) requestMsg.obj;
694d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        boolean taken = takeScreenshot(mContext, screenshotFile);
695d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        setTakingScreenshot(false);
696d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
6978648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme        Message.obtain(mMainHandler, MSG_SCREENSHOT_RESPONSE, requestMsg.arg1, taken ? 1 : 0,
6988648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme                screenshotFile).sendToTarget();
699d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
700bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
701d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void handleScreenshotResponse(Message resultMsg) {
702d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final boolean taken = resultMsg.arg2 != 0;
703d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(resultMsg.arg1);
704d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
705d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
706d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
707d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final File screenshotFile = new File((String) resultMsg.obj);
708d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
7095d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme        final String msg;
710d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (taken) {
711d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.addScreenshot(screenshotFile);
712c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            if (info.finished) {
713c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                Log.d(TAG, "Screenshot finished after bugreport; updating share notification");
714c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                info.renameScreenshots(mScreenshotsDir);
7155ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme                sendBugreportNotification(mContext, info, mTakingScreenshot);
716c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
7175d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            msg = mContext.getString(R.string.bugreport_screenshot_taken);
718d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
719d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // TODO: try again using Framework APIs instead of relying on screencap.
7205d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            msg = mContext.getString(R.string.bugreport_screenshot_failed);
7215d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
722d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
723d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Log.d(TAG, msg);
724d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
725d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
726d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
727d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Deletes all screenshots taken for a given bugreport.
728d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
729d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void deleteScreenshots(BugreportInfo info) {
730d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (File file : info.screenshotFiles) {
731d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Deleting screenshot file " + file);
732d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            file.delete();
733d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
734bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
735bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
736bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
737923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Finishes the service when it's not monitoring any more processes.
738923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
739923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void stopSelfWhenDone() {
740d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (mProcesses.size() > 0) {
741fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (DEBUG) Log.d(TAG, "Staying alive, waiting for IDs " + mProcesses);
742d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
74369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
744fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "No more processes to handle, shutting down");
745d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        stopSelf();
746923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
74769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
748bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
749bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
750bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
751fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void onBugreportFinished(int id, Intent intent) {
752af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
753af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        if (bugreportFile == null) {
754af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            // Should never happen, dumpstate always set the file.
755af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent);
756af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            return;
757af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        }
758fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        mInfoDialog.onBugreportFinished(id);
759fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo info = getInfo(id);
760d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
761d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED first.
762fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.v(TAG, "Creating info for untracked ID " + id);
763fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            info = new BugreportInfo(mContext, id);
764fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mProcesses.put(id, info);
765d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
766d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.renameScreenshots(mScreenshotsDir);
767af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        info.bugreportFile = bugreportFile;
768af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme
769510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        final int max = intent.getIntExtra(EXTRA_MAX, -1);
770510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        if (max != -1) {
771510e922e47fec69839dd48c5473540f93d79a508Felipe Leme            MetricsLogger.histogram(this, "dumpstate_duration", max);
772510e922e47fec69839dd48c5473540f93d79a508Felipe Leme            info.max = max;
773510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        }
774510e922e47fec69839dd48c5473540f93d79a508Felipe Leme
775d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final File screenshot = getFileExtra(intent, EXTRA_SCREENSHOT);
776d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (screenshot != null) {
777d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.addScreenshot(screenshot);
77846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
779d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.finished = true;
78069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
781d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Configuration conf = mContext.getResources().getConfiguration();
782923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
783d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            triggerLocalNotification(mContext, info);
784b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
785b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
786b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
787b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
78869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Responsible for triggering a notification that allows the user to start a "share" intent with
78946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * the bugreport. On watches we have other methods to allow the user to start this intent
79069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * (usually by triggering it on another connected device); we don't need to display the
79169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * notification in this case.
792b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
793d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void triggerLocalNotification(final Context context, final BugreportInfo info) {
79446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
79546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
796d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Toast.makeText(context, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
797fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            stopProgress(info.id);
798b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return;
799b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
800b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
80146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
802b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (!isPlainText) {
803b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Already zipped, send it right away.
8045ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme            sendBugreportNotification(context, info, mTakingScreenshot);
805b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
806b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Asynchronously zip the file first, then send it.
8075ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme            sendZippedBugreportNotification(context, info, mTakingScreenshot);
808b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
809b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
810b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
811b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Intent buildWarningIntent(Context context, Intent sendIntent) {
812b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Intent intent = new Intent(context, BugreportWarningActivity.class);
813b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
814b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return intent;
815b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
816b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
817b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
818b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Build {@link Intent} that can be used to share the given bugreport.
819b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
820bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static Intent buildSendIntent(Context context, BugreportInfo info) {
821bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // Files are kept on private storage, so turn into Uris that we can
822bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // grant temporary permissions for.
823bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final Uri bugreportUri = getUri(context, info.bugreportFile);
824bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
825b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
826b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final String mimeType = "application/vnd.android.bugreport";
827b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
828b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.addCategory(Intent.CATEGORY_DEFAULT);
829b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.setType(mimeType);
830b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
831c8e2b6092c0fbf87e71f81fd2cffbb29ff8d9039Felipe Leme        final String subject = !TextUtils.isEmpty(info.title) ?
832c8e2b6092c0fbf87e71f81fd2cffbb29ff8d9039Felipe Leme                info.title : bugreportUri.getLastPathSegment();
833bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
834b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
835b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
836b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
837b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // create the ClipData object with the attachments URIs.
838d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final StringBuilder messageBody = new StringBuilder("Build info: ")
839bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append(SystemProperties.get("ro.build.description"))
840bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append("\nSerial number: ")
841bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append(SystemProperties.get("ro.serialno"));
842bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        if (!TextUtils.isEmpty(info.description)) {
843bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            messageBody.append("\nDescription: ").append(info.description);
844bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
845bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
846b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final ClipData clipData = new ClipData(null, new String[] { mimeType },
847b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                new ClipData.Item(null, null, null, bugreportUri));
848b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
849d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (File screenshot : info.screenshotFiles) {
850d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Uri screenshotUri = getUri(context, screenshot);
851b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
852b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            attachments.add(screenshotUri);
853b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
854b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.setClipData(clipData);
855b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
856b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
857b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Account sendToAccount = findSendToAccount(context);
858b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (sendToAccount != null) {
859b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
860b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
861b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
862b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return intent;
863b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
864b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
865b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
86646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Shares the bugreport upon user's request by issuing a {@link Intent#ACTION_SEND_MULTIPLE}
86746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * intent, but issuing a warning dialog the first time.
868b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
869fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void shareBugreport(int id, BugreportInfo sharedInfo) {
8706605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SHARE);
871fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo info = getInfo(id);
872d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
873c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            // Service was terminated but notification persisted
874c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            info = sharedInfo;
875fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
876c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                    + mProcesses + "), using info from intent instead (" + info + ")");
8774f663f6c25488a83a438a01dfef8372aa4e6b2aaFelipe Leme        } else {
8784f663f6c25488a83a438a01dfef8372aa4e6b2aaFelipe Leme            Log.v(TAG, "shareBugReport(): id " + id + " info = " + info);
87946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
8804967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
88118b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        addDetailsToZipFile(mContext, info);
8824967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
883d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent sendIntent = buildSendIntent(mContext, info);
88446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent notifIntent;
885b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
886b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // Send through warning dialog by default
887d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (getWarningState(mContext, STATE_SHOW) == STATE_SHOW) {
888d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            notifIntent = buildWarningIntent(mContext, sendIntent);
889b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
890b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            notifIntent = sendIntent;
891b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
892b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
893b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
89446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        // Send the share intent...
895d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mContext.startActivity(notifIntent);
89646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
89746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        // ... and stop watching this process.
898fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        stopProgress(id);
89946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    }
90046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
90146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    /**
9022758d5d93970f26867d778c944605371e55b751eFelipe Leme     * Sends a notification indicating the bugreport has finished so use can share it.
90346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     */
9045ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme    private static void sendBugreportNotification(Context context, BugreportInfo info,
9055ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme            boolean takingScreenshot) {
90618b5892950b7f21e66c9268129323cbc0e865699Felipe Leme
90718b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        // Since adding the details can take a while, do it before notifying user.
90818b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        addDetailsToZipFile(context, info);
90918b5892950b7f21e66c9268129323cbc0e865699Felipe Leme
91046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE);
91146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        shareIntent.setClass(context, BugreportProgressService.class);
91246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        shareIntent.setAction(INTENT_BUGREPORT_SHARE);
913fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        shareIntent.putExtra(EXTRA_ID, info.id);
914c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        shareIntent.putExtra(EXTRA_INFO, info);
91546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
9165ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme        final String title, content;
9175ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme        if (takingScreenshot) {
9185ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme            title = context.getString(R.string.bugreport_finished_pending_screenshot_title,
9195ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme                    info.id);
9205ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme            content = context.getString(R.string.bugreport_finished_pending_screenshot_text);
9215ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme        } else {
9225ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme            title = context.getString(R.string.bugreport_finished_title, info.id);
9235ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme            content = context.getString(R.string.bugreport_finished_text);
9245ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme        }
925208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        final Notification.Builder builder = newBaseNotification(context)
92669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                .setContentTitle(title)
92769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                .setTicker(title)
9285ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme                .setContentText(content)
929fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                .setContentIntent(PendingIntent.getService(context, info.id, shareIntent,
930bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        PendingIntent.FLAG_UPDATE_CURRENT))
931208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme                .setDeleteIntent(newCancelIntent(context, info));
932b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
933bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        if (!TextUtils.isEmpty(info.name)) {
934bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            builder.setContentInfo(info.name);
935bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
936bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
937fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
938fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        NotificationManager.from(context).notify(TAG, info.id, builder.build());
939b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
940b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
941b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
9422758d5d93970f26867d778c944605371e55b751eFelipe Leme     * Sends a notification indicating the bugreport is being updated so the user can wait until it
9432758d5d93970f26867d778c944605371e55b751eFelipe Leme     * finishes - at this point there is nothing to be done other than waiting, hence it has no
9442758d5d93970f26867d778c944605371e55b751eFelipe Leme     * pending action.
9452758d5d93970f26867d778c944605371e55b751eFelipe Leme     */
946fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private static void sendBugreportBeingUpdatedNotification(Context context, int id) {
9472758d5d93970f26867d778c944605371e55b751eFelipe Leme        final String title = context.getString(R.string.bugreport_updating_title);
948208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        final Notification.Builder builder = newBaseNotification(context)
9492758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setContentTitle(title)
9502758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setTicker(title)
951208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme                .setContentText(context.getString(R.string.bugreport_updating_wait));
952208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        Log.v(TAG, "Sending 'Updating zip' notification for ID " + id + ": " + title);
953208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        NotificationManager.from(context).notify(TAG, id, builder.build());
954208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme    }
955208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme
956208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme    private static Notification.Builder newBaseNotification(Context context) {
957208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        return new Notification.Builder(context)
958208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme                .setCategory(Notification.CATEGORY_SYSTEM)
959208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
9602758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setLocalOnly(true)
9612758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setColor(context.getColor(
9622758d5d93970f26867d778c944605371e55b751eFelipe Leme                        com.android.internal.R.color.system_notification_accent_color));
9632758d5d93970f26867d778c944605371e55b751eFelipe Leme    }
9642758d5d93970f26867d778c944605371e55b751eFelipe Leme
9652758d5d93970f26867d778c944605371e55b751eFelipe Leme    /**
966b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Sends a zipped bugreport notification.
967b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
968b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static void sendZippedBugreportNotification(final Context context,
9695ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme            final BugreportInfo info, final boolean takingScreenshot) {
970b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        new AsyncTask<Void, Void, Void>() {
971b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            @Override
972b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            protected Void doInBackground(Void... params) {
9734967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                zipBugreport(info);
9745ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme                sendBugreportNotification(context, info, takingScreenshot);
975b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                return null;
976b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
977b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }.execute();
978b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
979b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
980b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
981b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Zips a bugreport file, returning the path to the new file (or to the
982b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * original in case of failure).
983b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
9844967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void zipBugreport(BugreportInfo info) {
9854967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final String bugreportPath = info.bugreportFile.getAbsolutePath();
9864967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final String zippedPath = bugreportPath.replace(".txt", ".zip");
987b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
9884967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File bugreportZippedFile = new File(zippedPath);
9894967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        try (InputStream is = new FileInputStream(info.bugreportFile);
99069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                ZipOutputStream zos = new ZipOutputStream(
99169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
9924967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, info.bugreportFile.getName(), is);
9934967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // Delete old file
9944967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            final boolean deleted = info.bugreportFile.delete();
995b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            if (deleted) {
996b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
997b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            } else {
998b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
999b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
10004967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            info.bugreportFile = bugreportZippedFile;
1001b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } catch (IOException e) {
100269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            Log.e(TAG, "exception zipping file " + zippedPath, e);
1003b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1004b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1005b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1006b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
10074967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * Adds the user-provided info into the bugreport zip file.
10084967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * <p>
10094967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * If user provided a title, it will be saved into a {@code title.txt} entry; similarly, the
10104967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * description will be saved on {@code description.txt}.
10114967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     */
101218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme    private static void addDetailsToZipFile(Context context, BugreportInfo info) {
1013c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        if (info.bugreportFile == null) {
1014c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            // One possible reason is a bug in the Parcelization code.
1015af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
1016c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return;
1017c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1018b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme        if (TextUtils.isEmpty(info.title) && TextUtils.isEmpty(info.description)) {
1019b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme            Log.d(TAG, "Not touching zip file since neither title nor description are set");
1020b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme            return;
1021b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme        }
102218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        if (info.addedDetailsToZip || info.addingDetailsToZip) {
102318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme            Log.d(TAG, "Already added details to zip file for " + info);
102418b5892950b7f21e66c9268129323cbc0e865699Felipe Leme            return;
102518b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        }
102618b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        info.addingDetailsToZip = true;
10272758d5d93970f26867d778c944605371e55b751eFelipe Leme
10284967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        // It's not possible to add a new entry into an existing file, so we need to create a new
10294967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        // zip, copy all entries, then rename it.
1030fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        sendBugreportBeingUpdatedNotification(context, info.id); // ...and that takes time
10314967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File dir = info.bugreportFile.getParentFile();
10324967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
10332758d5d93970f26867d778c944605371e55b751eFelipe Leme        Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
10344967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        try (ZipFile oldZip = new ZipFile(info.bugreportFile);
10354967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
10364967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10374967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // First copy contents from original zip.
10384967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Enumeration<? extends ZipEntry> entries = oldZip.entries();
10394967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            while (entries.hasMoreElements()) {
10404967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                final ZipEntry entry = entries.nextElement();
10414967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                final String entryName = entry.getName();
10424967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                if (!entry.isDirectory()) {
10434967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                    addEntry(zos, entryName, entry.getTime(), oldZip.getInputStream(entry));
10444967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                } else {
10454967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                    Log.w(TAG, "skipping directory entry: " + entryName);
10464967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                }
10474967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            }
10484967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10494967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // Then add the user-provided info.
10504967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, "title.txt", info.title);
10514967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, "description.txt", info.description);
10524967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        } catch (IOException e) {
105318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme            info.addingDetailsToZip = false;
10544967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Log.e(TAG, "exception zipping file " + tmpZip, e);
10554967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            return;
10564967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
10574967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10584967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (!tmpZip.renameTo(info.bugreportFile)) {
10594967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
10604967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
106118b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        info.addedDetailsToZip = true;
106218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        info.addingDetailsToZip = false;
10634967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
10644967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10654967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entry, String text)
10664967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            throws IOException {
10674967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (DEBUG) Log.v(TAG, "adding entry '" + entry + "': " + text);
10684967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (!TextUtils.isEmpty(text)) {
10694967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, entry, new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)));
10704967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
10714967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
10724967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10734967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entryName, InputStream is)
10744967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            throws IOException {
10754967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        addEntry(zos, entryName, System.currentTimeMillis(), is);
10764967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
10774967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10784967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entryName, long timestamp,
10794967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            InputStream is) throws IOException {
10804967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final ZipEntry entry = new ZipEntry(entryName);
10814967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        entry.setTime(timestamp);
10824967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        zos.putNextEntry(entry);
10834967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final int totalBytes = Streams.copy(is, zos);
10844967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (DEBUG) Log.v(TAG, "size of '" + entryName + "' entry: " + totalBytes + " bytes");
10854967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        zos.closeEntry();
10864967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
10874967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10884967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    /**
1089b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Find the best matching {@link Account} based on build properties.
1090b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
1091b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Account findSendToAccount(Context context) {
1092b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final AccountManager am = (AccountManager) context.getSystemService(
1093b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Context.ACCOUNT_SERVICE);
1094b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1095b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
1096b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (!preferredDomain.startsWith("@")) {
1097b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            preferredDomain = "@" + preferredDomain;
1098b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1099b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1100b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Account[] accounts = am.getAccounts();
1101b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        Account foundAccount = null;
1102b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        for (Account account : accounts) {
1103b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
1104b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                if (!preferredDomain.isEmpty()) {
1105b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // if we have a preferred domain and it matches, return; otherwise keep
1106b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // looking
1107b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    if (account.name.endsWith(preferredDomain)) {
1108b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        return account;
1109b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    } else {
1110b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        foundAccount = account;
1111b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    }
1112b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // if we don't have a preferred domain, just return since it looks like
1113b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // an email address
1114b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                } else {
1115b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    return account;
1116b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                }
1117b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
1118b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1119b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return foundAccount;
1120b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1121b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1122226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski    static Uri getUri(Context context, File file) {
1123b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
1124b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1125b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1126b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static File getFileExtra(Intent intent, String key) {
1127b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final String path = intent.getStringExtra(key);
1128b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (path != null) {
1129b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return new File(path);
1130b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
1131b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return null;
1132b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1133b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
113469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
1135bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static boolean setSystemProperty(String key, String value) {
1136bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        try {
113785ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            if (DEBUG) Log.v(TAG, "Setting system property " + key + " to " + value);
1138bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            SystemProperties.set(key, value);
1139bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        } catch (IllegalArgumentException e) {
1140bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            Log.e(TAG, "Could not set property " + key + " to " + value, e);
1141bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            return false;
1142bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1143bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return true;
1144bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1145bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1146bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1147bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
1148bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1149bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private boolean setBugreportNameProperty(int pid, String name) {
1150bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        Log.d(TAG, "Updating bugreport name to " + name);
1151bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
1152bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return setSystemProperty(key, name);
1153bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1154bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1155bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1156bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Updates the user-provided details of a bugreport.
1157bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1158fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void updateBugreportInfo(int id, String name, String title, String description) {
1159fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
1160d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
1161d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
1162d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
11636605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        if (title != null && !title.equals(info.title)) {
11646605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_TITLE_CHANGED);
11656605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        }
1166d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.title = title;
11676605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        if (description != null && !description.equals(info.description)) {
11686605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_DESCRIPTION_CHANGED);
11696605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        }
1170d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.description = description;
11711eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        if (name != null && !name.equals(info.name)) {
11726605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_NAME_CHANGED);
1173d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.name = name;
1174d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            updateProgress(info);
1175d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1176d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1177d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1178d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void collapseNotificationBar() {
1179d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
1180d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1181d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1182d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static Looper newLooper(String name) {
1183d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final HandlerThread thread = new HandlerThread(name, THREAD_PRIORITY_BACKGROUND);
1184d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        thread.start();
1185d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return thread.getLooper();
1186d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1187d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1188d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
1189d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Takes a screenshot and save it to the given location.
1190d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
1191d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static boolean takeScreenshot(Context context, String screenshotFile) {
1192d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final ProcessBuilder screencap = new ProcessBuilder()
1193d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .command("/system/bin/screencap", "-p", screenshotFile);
1194d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Log.d(TAG, "Taking screenshot using " + screencap.command());
1195d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        try {
1196d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int exitValue = screencap.start().waitFor();
1197d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (exitValue == 0) {
1198aa00f2d909dcc48b61b9338cd2ab7c33850a69d9Felipe Leme                ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)).vibrate(150);
1199d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return true;
1200bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1201d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.e(TAG, "screencap (" + screencap.command() + ") failed: " + exitValue);
1202d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } catch (IOException e) {
1203d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.e(TAG, "screencap (" + screencap.command() + ") failed", e);
1204d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } catch (InterruptedException e) {
1205d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.w(TAG, "Thread interrupted while screencap still running");
1206d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Thread.currentThread().interrupt();
1207bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1208d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return false;
1209bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1210bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1211bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1212bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Checks whether a character is valid on bugreport names.
1213bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1214bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    @VisibleForTesting
1215bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static boolean isValid(char c) {
1216bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
1217bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                || c == '_' || c == '-';
1218bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1219bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1220bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1221bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Helper class encapsulating the UI elements and logic used to display a dialog where user
1222bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * can change the details of a bugreport.
1223bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1224bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private final class BugreportInfoDialog {
1225bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoName;
1226bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoTitle;
1227bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoDescription;
1228bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private AlertDialog mDialog;
1229bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private Button mOkButton;
1230fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        private int mId;
1231bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private int mPid;
1232bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1233bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1234bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Last "committed" value of the bugreport name.
1235bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1236bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Once initially set, it's only updated when user clicks the OK button.
1237bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1238bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private String mSavedName;
1239bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1240bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1241bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Last value of the bugreport name as entered by the user.
1242bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1243bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Every time it's changed the equivalent system property is changed as well, but if the
1244bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
1245bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1246bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
1247bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * user changed the name but didn't clicked OK yet (for example, because the user is typing
1248bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * the description). The only drawback is that if the user changes the name while
1249bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
1250bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
1251bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
1252bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * such drawback.
1253bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1254bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private String mTempName;
1255bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1256bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1257bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Sets its internal state and displays the dialog.
1258bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
12596605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        private void initialize(final Context context, BugreportInfo info) {
1260262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme            final String dialogTitle =
1261262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                    context.getString(R.string.bugreport_info_dialog_title, info.id);
1262bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // First initializes singleton.
1263bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mDialog == null) {
1264bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                @SuppressLint("InflateParams")
1265bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                // It's ok pass null ViewRoot on AlertDialogs.
1266bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                final View view = View.inflate(context, R.layout.dialog_bugreport_info, null);
1267bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1268bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName = (EditText) view.findViewById(R.id.name);
1269bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoTitle = (EditText) view.findViewById(R.id.title);
1270bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoDescription = (EditText) view.findViewById(R.id.description);
1271bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1272bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
1273bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1274bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    @Override
1275bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    public void onFocusChange(View v, boolean hasFocus) {
1276bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        if (hasFocus) {
1277bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                            return;
1278bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        }
1279bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        sanitizeName();
1280bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    }
1281bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                });
1282bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1283bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mDialog = new AlertDialog.Builder(context)
1284bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setView(view)
1285262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                        .setTitle(dialogTitle)
1286bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setCancelable(false)
1287bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                        .setPositiveButton(context.getString(R.string.save),
1288bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                null)
1289bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setNegativeButton(context.getString(com.android.internal.R.string.cancel),
1290bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                new DialogInterface.OnClickListener()
1291bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                {
1292bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    @Override
1293bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    public void onClick(DialogInterface dialog, int id)
1294bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    {
12956605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                                        MetricsLogger.action(context,
12966605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                                                MetricsEvent.ACTION_BUGREPORT_DETAILS_CANCELED);
1297bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                        if (!mTempName.equals(mSavedName)) {
1298bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            // Must restore dumpstate's name since it was changed
1299bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            // before user clicked OK.
1300bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            setBugreportNameProperty(mPid, mSavedName);
1301bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                        }
1302bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    }
1303bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                })
1304bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .create();
1305bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1306bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mDialog.getWindow().setAttributes(
1307bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        new WindowManager.LayoutParams(
1308bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG));
1309bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1310262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme            } else {
1311262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                // Re-use view, but reset fields first.
1312262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mDialog.setTitle(dialogTitle);
1313262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoName.setText(null);
1314262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoTitle.setText(null);
1315262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoDescription.setText(null);
1316bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1317bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1318bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // Then set fields.
1319fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mSavedName = mTempName = info.name;
1320fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mId = info.id;
1321fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mPid = info.pid;
1322fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.name)) {
1323fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoName.setText(info.name);
1324bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1325fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.title)) {
1326fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoTitle.setText(info.title);
1327bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1328fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.description)) {
1329fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoDescription.setText(info.description);
1330bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1331bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1332bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // And finally display it.
1333bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mDialog.show();
1334bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1335bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // TODO: in a traditional AlertDialog, when the positive button is clicked the
1336bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // dialog is always closed, but we need to validate the name first, so we need to
1337bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // get a reference to it, which is only available after it's displayed.
1338bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // It would be cleaner to use a regular dialog instead, but let's keep this
1339bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // workaround for now and change it later, when we add another button to take
1340bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // extra screenshots.
1341bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mOkButton == null) {
1342bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mOkButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
1343bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mOkButton.setOnClickListener(new View.OnClickListener() {
1344bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1345bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    @Override
1346bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    public void onClick(View view) {
13476605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                        MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
1348bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        sanitizeName();
1349bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String name = mInfoName.getText().toString();
1350bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String title = mInfoTitle.getText().toString();
1351bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String description = mInfoDescription.getText().toString();
1352bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1353fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        updateBugreportInfo(mId, name, title, description);
1354bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        mDialog.dismiss();
1355bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    }
1356bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                });
1357bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1358bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1359bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1360bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1361bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Sanitizes the user-provided value for the {@code name} field, automatically replacing
1362bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * invalid characters if necessary.
1363bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1364d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        private void sanitizeName() {
1365bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            String name = mInfoName.getText().toString();
1366bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (name.equals(mTempName)) {
1367bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
1368bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                return;
1369bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1370bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            final StringBuilder safeName = new StringBuilder(name.length());
1371bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            boolean changed = false;
1372bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            for (int i = 0; i < name.length(); i++) {
1373bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                final char c = name.charAt(i);
1374bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                if (isValid(c)) {
1375bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    safeName.append(c);
1376bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                } else {
1377bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    changed = true;
1378bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    safeName.append('_');
1379bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                }
1380bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1381bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (changed) {
1382bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                Log.v(TAG, "changed invalid name '" + name + "' to '" + safeName + "'");
1383bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                name = safeName.toString();
1384bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(name);
1385bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1386bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mTempName = name;
1387bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1388bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // Must update system property for the cases where dumpstate finishes
1389bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // while the user is still entering other fields (like title or
1390bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // description)
139185ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            setBugreportNameProperty(mPid, name);
1392bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1393bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1394bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme       /**
1395bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Notifies the dialog that the bugreport has finished so it disables the {@code name}
1396bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * field.
1397bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>Once the bugreport is finished dumpstate has already generated the final files, so
1398bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * changing the name would have no effect.
1399bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1400fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        private void onBugreportFinished(int id) {
1401bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mInfoName != null) {
1402bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setEnabled(false);
1403bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(mSavedName);
1404bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1405bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1406bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1407bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1408bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
140969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /**
141046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Information about a bugreport process while its in progress.
141169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     */
1412c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    private static final class BugreportInfo implements Parcelable {
1413719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        private final Context context;
1414719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme
141569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
1416fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme         * Sequential, user-friendly id used to identify the bugreport.
1417fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme         */
1418fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final int id;
1419fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme
1420fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        /**
142146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * {@code pid} of the {@code dumpstate} process generating the bugreport.
142269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
142369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        final int pid;
142469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
142569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
142646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Name of the bugreport, will be used to rename the final files.
142769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * <p>
142846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Initial value is the bugreport filename reported by {@code dumpstate}, but user can
142969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * change it later to a more meaningful name.
143069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
1431719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        String name;
143269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
143369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
1434bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * User-provided, one-line summary of the bug; when set, will be used as the subject
1435bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
1436bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1437bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        String title;
1438bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1439bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1440bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * User-provided, detailed description of the bugreport; when set, will be added to the body
1441bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
1442bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1443bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        String description;
1444bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1445bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
144646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Maximum progress of the bugreport generation.
144769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
1448719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        int max;
144969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
145069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
145146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Current progress of the bugreport generation.
145269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
145369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        int progress;
145469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
145569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
145669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * Time of the last progress update.
145769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
145869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        long lastUpdate = System.currentTimeMillis();
145969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
146046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
1461c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme         * Time of the last progress update when Parcel was created.
1462c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme         */
1463c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        String formattedLastUpdate;
1464c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1465c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        /**
146646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Path of the main bugreport file.
146746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
146846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        File bugreportFile;
146946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
147046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
1471d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Path of the screenshot files.
147246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1473d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        List<File> screenshotFiles = new ArrayList<>(1);
147446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
147546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
147646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Whether dumpstate sent an intent informing it has finished.
147746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
147846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        boolean finished;
147946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
148046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
148118b5892950b7f21e66c9268129323cbc0e865699Felipe Leme         * Whether the details entries have been added to the bugreport yet.
148218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme         */
148318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        boolean addingDetailsToZip;
148418b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        boolean addedDetailsToZip;
148518b5892950b7f21e66c9268129323cbc0e865699Felipe Leme
148618b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        /**
1487d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Internal counter used to name screenshot files.
1488d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1489d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        int screenshotCounter;
1490d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1491d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
149246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
149346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1494fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo(Context context, int id, int pid, String name, int max) {
1495719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme            this.context = context;
1496fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            this.id = id;
149769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.pid = pid;
149869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.name = name;
149969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.max = max;
150069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
150169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
150246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
150346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
150446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * without a previous call to BUGREPORT_STARTED.
150546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1506fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo(Context context, int id) {
1507fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            this(context, id, id, null, 0);
150846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            this.finished = true;
150946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
151046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
1511d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1512d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Gets the name for next screenshot file.
1513d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1514d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        String getPathNextScreenshot() {
1515d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotCounter ++;
1516d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return "screenshot-" + pid + "-" + screenshotCounter + ".png";
1517d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1518d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1519d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1520d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Saves the location of a taken screenshot so it can be sent out at the end.
1521d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1522d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        void addScreenshot(File screenshot) {
1523d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotFiles.add(screenshot);
1524d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1525d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1526d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1527d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Rename all screenshots files so that they contain the user-generated name instead of pid.
1528d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1529d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        void renameScreenshots(File screenshotDir) {
1530d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (TextUtils.isEmpty(name)) {
1531d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
1532d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
1533d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
1534d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            for (File oldFile : screenshotFiles) {
1535d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final String oldName = oldFile.getName();
153685ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme                final String newName = oldName.replaceFirst(Integer.toString(pid), name);
1537d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final File newFile;
1538d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (!newName.equals(oldName)) {
1539d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    final File renamedFile = new File(screenshotDir, newName);
15404f663f6c25488a83a438a01dfef8372aa4e6b2aaFelipe Leme                    Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
1541d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
1542d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                } else {
1543d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
1544d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    newFile = oldFile;
1545d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                }
1546d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                renamedFiles.add(newFile);
1547d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
1548d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotFiles = renamedFiles;
1549d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1550d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
155169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        String getFormattedLastUpdate() {
1552c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            if (context == null) {
1553c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                // Restored from Parcel
1554c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return formattedLastUpdate == null ?
1555c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                        Long.toString(lastUpdate) : formattedLastUpdate;
1556c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1557719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme            return DateUtils.formatDateTime(context, lastUpdate,
1558719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
155969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
156069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
156169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        @Override
156269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        public String toString() {
156369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final float percent = ((float) progress * 100 / max);
1564fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            return "id: " + id + ", pid: " + pid + ", name: " + name + ", finished: " + finished
1565bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    + "\n\ttitle: " + title + "\n\tdescription: " + description
1566d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    + "\n\tfile: " + bugreportFile + "\n\tscreenshots: " + screenshotFiles
1567510e922e47fec69839dd48c5473540f93d79a508Felipe Leme                    + "\n\tprogress: " + progress + "/" + max + " (" + percent + ")"
156818b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + "\n\tlast_update: " + getFormattedLastUpdate()
156918b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + "\naddingDetailsToZip: " + addingDetailsToZip
157018b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + " addedDetailsToZip: " + addedDetailsToZip;
157169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
1572c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1573c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        // Parcelable contract
1574c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        protected BugreportInfo(Parcel in) {
1575c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            context = null;
1576fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            id = in.readInt();
1577c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            pid = in.readInt();
1578c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            name = in.readString();
1579c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            title = in.readString();
1580c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            description = in.readString();
1581c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            max = in.readInt();
1582c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            progress = in.readInt();
1583c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            lastUpdate = in.readLong();
1584c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            formattedLastUpdate = in.readString();
1585c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            bugreportFile = readFile(in);
1586c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1587c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            int screenshotSize = in.readInt();
1588c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            for (int i = 1; i <= screenshotSize; i++) {
1589c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                  screenshotFiles.add(readFile(in));
1590c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1591c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1592c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            finished = in.readInt() == 1;
1593c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            screenshotCounter = in.readInt();
1594c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1595c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1596c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        @Override
1597c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public void writeToParcel(Parcel dest, int flags) {
1598fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            dest.writeInt(id);
1599c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(pid);
1600c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(name);
1601c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(title);
1602c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(description);
1603c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(max);
1604c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(progress);
1605c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeLong(lastUpdate);
1606c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(getFormattedLastUpdate());
1607c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            writeFile(dest, bugreportFile);
1608c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1609c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(screenshotFiles.size());
1610c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            for (File screenshotFile : screenshotFiles) {
1611c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                writeFile(dest, screenshotFile);
1612c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1613c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1614c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(finished ? 1 : 0);
1615c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(screenshotCounter);
1616c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1617c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1618c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        @Override
1619c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public int describeContents() {
1620c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return 0;
1621c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1622c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1623c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        private void writeFile(Parcel dest, File file) {
1624c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(file == null ? null : file.getPath());
1625c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1626c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1627c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        private File readFile(Parcel in) {
1628c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            final String path = in.readString();
1629c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return path == null ? null : new File(path);
1630c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1631c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1632c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public static final Parcelable.Creator<BugreportInfo> CREATOR =
1633c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                new Parcelable.Creator<BugreportInfo>() {
1634c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            public BugreportInfo createFromParcel(Parcel source) {
1635c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return new BugreportInfo(source);
1636c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1637c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1638c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            public BugreportInfo[] newArray(int size) {
1639c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return new BugreportInfo[size];
1640c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1641c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        };
1642c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
164369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
1644b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme}
1645