BugreportProgressService.java revision aa00f2d909dcc48b61b9338cd2ab7c33850a69d9
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;
59d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport android.content.ContextWrapper;
60bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.content.DialogInterface;
61b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.Intent;
62b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.res.Configuration;
63b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.net.Uri;
64b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.AsyncTask;
6569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Handler;
6669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.HandlerThread;
67b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.IBinder;
6869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Looper;
6969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Message;
70c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Lemeimport android.os.Parcel;
7169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Parcelable;
72b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.SystemProperties;
73d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport android.os.Vibrator;
74b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.support.v4.content.FileProvider;
75bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.text.TextUtils;
7669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.text.format.DateUtils;
77b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.util.Log;
78b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.util.Patterns;
7969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.util.SparseArray;
80bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.View;
81bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.WindowManager;
82bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.View.OnFocusChangeListener;
83bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.inputmethod.EditorInfo;
84bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.widget.Button;
85bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.widget.EditText;
86b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.widget.Toast;
87b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
8869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme/**
8946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme * Service used to keep progress of bugreport processes ({@code dumpstate}).
9069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <p>
9169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * The workflow is:
9269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
93fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with a sequential id,
94fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme * its pid, and the estimated total effort.
9569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
9669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Upon start, this service:
9769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
9869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
9969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
10069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>If the progress changed, it updates the system notification.
10169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
10269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>As {@code dumpstate} progresses, it updates the system property.
10369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
10469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
10569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * turn:
10669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
10746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme * <li>Updates the system notification so user can share the bugreport.
10869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Stops monitoring that {@code dumpstate} process.
10969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Stops itself if it doesn't have any process left to monitor.
11069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
11169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
11269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme */
113b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemepublic class BugreportProgressService extends Service {
114c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    private static final String TAG = "BugreportProgressService";
11569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final boolean DEBUG = false;
116b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
117b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static final String AUTHORITY = "com.android.shell";
118b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
11946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    // External intents sent by dumpstate.
12069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
12169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";
122226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski    static final String INTENT_REMOTE_BUGREPORT_FINISHED =
123226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski            "android.intent.action.REMOTE_BUGREPORT_FINISHED";
12446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
12546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    // Internal intents used on notification actions.
1269cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme    static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
12746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
128bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String INTENT_BUGREPORT_INFO_LAUNCH =
129bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            "android.intent.action.BUGREPORT_INFO_LAUNCH";
130d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    static final String INTENT_BUGREPORT_SCREENSHOT =
131d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            "android.intent.action.BUGREPORT_SCREENSHOT";
13269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
133b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
134b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
135fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    static final String EXTRA_ID = "android.intent.extra.ID";
13669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_PID = "android.intent.extra.PID";
13769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_MAX = "android.intent.extra.MAX";
13869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_NAME = "android.intent.extra.NAME";
139bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String EXTRA_TITLE = "android.intent.extra.TITLE";
140bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
14169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
142c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    static final String EXTRA_INFO = "android.intent.extra.INFO";
14369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
14469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final int MSG_SERVICE_COMMAND = 1;
14569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final int MSG_POLL = 2;
146d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_DELAYED_SCREENSHOT = 3;
147d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_SCREENSHOT_REQUEST = 4;
148d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_SCREENSHOT_RESPONSE = 5;
149d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1508648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme    // Passed to Message.obtain() when msg.arg2 is not used.
1518648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme    private static final int UNUSED_ARG2 = -2;
1528648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme
153d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
154d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Delay before a screenshot is taken.
155d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
156d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Should be at least 3 seconds, otherwise its toast might show up in the screenshot.
157d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
158d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    static final int SCREENSHOT_DELAY_SECONDS = 3;
15969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
16069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** Polling frequency, in milliseconds. */
161bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final long POLLING_FREQUENCY = 2 * DateUtils.SECOND_IN_MILLIS;
16269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
16369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
1641eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme    private static final long INACTIVITY_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
16569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
166719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    /** System properties used for monitoring progress. */
167719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String DUMPSTATE_PREFIX = "dumpstate.";
168719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String PROGRESS_SUFFIX = ".progress";
169719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String MAX_SUFFIX = ".max";
170bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static final String NAME_SUFFIX = ".name";
1719cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme
172bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /** System property (and value) used to stop dumpstate. */
173d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    // TODO: should call ActiveManager API instead
174719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String CTL_STOP = "ctl.stop";
1754cc863338d5e43b6189e05498d7cb53ebba135e1Felipe Leme    private static final String BUGREPORT_SERVICE = "bugreportplus";
17669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
177d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
178d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Directory on Shell's data storage where screenshots will be stored.
179d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
180d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Must be a path supported by its FileProvider.
181d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
182d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final String SCREENSHOT_DIR = "bugreports";
183d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
184fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    /** Managed dumpstate processes (keyed by id) */
18569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>();
18669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
187d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private Context mContext;
188d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private ServiceHandler mMainHandler;
189d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private ScreenshotHandler mScreenshotHandler;
19069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
191bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog();
192bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
193d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private File mScreenshotsDir;
194d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
195d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
196d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Flag indicating whether a screenshot is being taken.
197d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
198d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * This is the only state that is shared between the 2 handlers and hence must have synchronized
199d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * access.
200d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
201d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private boolean mTakingScreenshot;
202d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
20369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
20469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void onCreate() {
205d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mContext = getApplicationContext();
206d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler = new ServiceHandler("BugreportProgressServiceMainThread");
207d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
208d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
209d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotsDir = new File(new ContextWrapper(mContext).getFilesDir(), SCREENSHOT_DIR);
210d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (!mScreenshotsDir.exists()) {
211d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots");
212d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (!mScreenshotsDir.mkdir()) {
213d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.w(TAG, "Could not create directory " + mScreenshotsDir);
214d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
215d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
21669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
217b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
218b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    @Override
219b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    public int onStartCommand(Intent intent, int flags, int startId) {
220b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (intent != null) {
22169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            // Handle it in a separate thread.
222d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Message msg = mMainHandler.obtainMessage();
22369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            msg.what = MSG_SERVICE_COMMAND;
22469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            msg.obj = intent;
225d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mMainHandler.sendMessage(msg);
226b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
22769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
22869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        // If service is killed it cannot be recreated because it would not know which
229fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        // dumpstate IDs it would have to watch.
230b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return START_NOT_STICKY;
231b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
232b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
233b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    @Override
234b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    public IBinder onBind(Intent intent) {
235b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return null;
236b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
237b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
23869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
23969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void onDestroy() {
240d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler.getLooper().quit();
241d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler.getLooper().quit();
24269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        super.onDestroy();
24369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
244b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
24569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
24669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
247d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int size = mProcesses.size();
248d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (size == 0) {
249d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            writer.printf("No monitored processes");
250d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
251d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
252d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        writer.printf("Monitored dumpstate processes\n");
253d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        writer.printf("-----------------------------\n");
254d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (int i = 0; i < size; i++) {
255d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            writer.printf("%s\n", mProcesses.valueAt(i));
25669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
25769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
25869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
259d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
260d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Main thread used to handle all requests but taking screenshots.
261d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
26269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private final class ServiceHandler extends Handler {
263d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public ServiceHandler(String name) {
264d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            super(newLooper(name));
26569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
26669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
26769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        @Override
26869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        public void handleMessage(Message msg) {
26969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (msg.what == MSG_POLL) {
270923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                poll();
27169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
27269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
27369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
274d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what == MSG_DELAYED_SCREENSHOT) {
275d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                takeScreenshot(msg.arg1, msg.arg2);
276d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
277d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
278d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
279d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what == MSG_SCREENSHOT_RESPONSE) {
280d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                handleScreenshotResponse(msg);
281d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
282d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
283d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
28469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (msg.what != MSG_SERVICE_COMMAND) {
28569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                // Sanity check.
28669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                Log.e(TAG, "Invalid message type: " + msg.what);
28769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
28869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
28969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
29046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            // At this point it's handling onStartCommand(), with the intent passed as an Extra.
29169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (!(msg.obj instanceof Intent)) {
29269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                // Sanity check.
293af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                Log.wtf(TAG, "handleMessage(): invalid msg.obj type: " + msg.obj);
29469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
29569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
29669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
29746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final Intent intent;
29846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            if (parcel instanceof Intent) {
29946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                // The real intent was passed to BugreportReceiver, which delegated to the service.
30046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                intent = (Intent) parcel;
30146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            } else {
30246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                intent = (Intent) msg.obj;
30369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
30469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final String action = intent.getAction();
30546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final int pid = intent.getIntExtra(EXTRA_PID, 0);
30685ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            final int id = intent.getIntExtra(EXTRA_ID, 0);
30746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final int max = intent.getIntExtra(EXTRA_MAX, -1);
30846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final String name = intent.getStringExtra(EXTRA_NAME);
30969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
310fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (DEBUG)
311fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id + ", pid: "
312fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        + pid + ", max: " + max);
31369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            switch (action) {
31469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                case INTENT_BUGREPORT_STARTED:
315fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    if (!startProgress(name, id, pid, max)) {
31669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        stopSelfWhenDone();
31769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        return;
31869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    }
31946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    poll();
32069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    break;
32169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                case INTENT_BUGREPORT_FINISHED:
322fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    if (id == 0) {
32369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
32469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        // out-of-sync dumpstate process.
325fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        Log.w(TAG, "Missing " + EXTRA_ID + " on intent " + intent);
32669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    }
327fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    onBugreportFinished(id, intent);
32846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    break;
329bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                case INTENT_BUGREPORT_INFO_LAUNCH:
330fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    launchBugreportInfoDialog(id);
331bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    break;
332d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                case INTENT_BUGREPORT_SCREENSHOT:
333fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    takeScreenshot(id, true);
334d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    break;
33546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                case INTENT_BUGREPORT_SHARE:
336fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    shareBugreport(id, (BugreportInfo) intent.getParcelableExtra(EXTRA_INFO));
33769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    break;
3389cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                case INTENT_BUGREPORT_CANCEL:
339fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    cancel(id);
3409cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                    break;
34169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                default:
34269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    Log.w(TAG, "Unsupported intent: " + action);
34369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
34469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            return;
34569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
34669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
34769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
348923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        private void poll() {
349923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            if (pollProgress()) {
350923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                // Keep polling...
351923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
35246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            } else {
35346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                Log.i(TAG, "Stopped polling");
35469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
355923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
356923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
35769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
358923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
359d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Separate thread used only to take screenshots so it doesn't block the main thread.
360d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
361d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private final class ScreenshotHandler extends Handler {
362d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public ScreenshotHandler(String name) {
363d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            super(newLooper(name));
364d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
365d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
366d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        @Override
367d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public void handleMessage(Message msg) {
368d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what != MSG_SCREENSHOT_REQUEST) {
369d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.e(TAG, "Invalid message type: " + msg.what);
370d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
371d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
372d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            handleScreenshotRequest(msg);
373d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
374d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
375d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
376fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private BugreportInfo getInfo(int id) {
377fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = mProcesses.get(id);
378d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
379fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "Not monitoring process with ID " + id);
380d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
381d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return info;
382d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
383d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
384d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
385923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Creates the {@link BugreportInfo} for a process and issue a system notification to
386923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * indicate its progress.
387923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     *
388923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * @return whether it succeeded or not.
389923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
390fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private boolean startProgress(String name, int id, int pid, int max) {
391923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (name == null) {
392923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
393923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
394fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (id == -1) {
395fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.e(TAG, "Missing " + EXTRA_ID + " on start intent");
396fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            return false;
397fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        }
398923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (pid == -1) {
399923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
400923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return false;
401923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
402923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (max <= 0) {
403923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
404923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return false;
40569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
40669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
407fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = new BugreportInfo(mContext, id, pid, name, max);
408fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (mProcesses.indexOfKey(id) >= 0) {
409fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "ID " + id + " already watched");
410d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
411fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mProcesses.put(info.id, info);
412923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
413d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        // Take initial screenshot.
414fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        takeScreenshot(id, false);
415923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        updateProgress(info);
416923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        return true;
417923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
41869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
419923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
42046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Updates the system notification for a given bugreport.
421923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
422923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void updateProgress(BugreportInfo info) {
423923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (info.max <= 0 || info.progress < 0) {
424923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Invalid progress values for " + info);
425923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return;
42669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
42769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
428923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final NumberFormat nf = NumberFormat.getPercentInstance();
429923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        nf.setMinimumFractionDigits(2);
430923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        nf.setMaximumFractionDigits(2);
431923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final String percentText = nf.format((double) info.progress / info.max);
432d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Action cancelAction = new Action.Builder(null, mContext.getString(
433d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                com.android.internal.R.string.cancel), newCancelIntent(mContext, info)).build();
434d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent infoIntent = new Intent(mContext, BugreportProgressService.class);
435bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        infoIntent.setAction(INTENT_BUGREPORT_INFO_LAUNCH);
436fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        infoIntent.putExtra(EXTRA_ID, info.id);
437db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme        final PendingIntent infoPendingIntent =
438db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme                PendingIntent.getService(mContext, info.id, infoIntent,
439db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme                PendingIntent.FLAG_UPDATE_CURRENT);
440bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final Action infoAction = new Action.Builder(null,
441d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                mContext.getString(R.string.bugreport_info_action),
442db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme                infoPendingIntent).build();
443d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent screenshotIntent = new Intent(mContext, BugreportProgressService.class);
444d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        screenshotIntent.setAction(INTENT_BUGREPORT_SCREENSHOT);
445fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        screenshotIntent.putExtra(EXTRA_ID, info.id);
446d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        PendingIntent screenshotPendingIntent = mTakingScreenshot ? null : PendingIntent
447fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                .getService(mContext, info.id, screenshotIntent,
448d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                        PendingIntent.FLAG_UPDATE_CURRENT);
449d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Action screenshotAction = new Action.Builder(null,
450d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                mContext.getString(R.string.bugreport_screenshot_action),
451d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                screenshotPendingIntent).build();
452d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
453fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final String title = mContext.getString(R.string.bugreport_in_progress_title, info.id);
454923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
455923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final String name =
456d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                info.name != null ? info.name : mContext.getString(R.string.bugreport_unnamed);
457923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
458d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Notification notification = new Notification.Builder(mContext)
459923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
460923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentTitle(title)
461923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setTicker(title)
462923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentText(name)
463923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentInfo(percentText)
464923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setProgress(info.max, info.progress, false)
465923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setOngoing(true)
466923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setLocalOnly(true)
467d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .setColor(mContext.getColor(
468923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                        com.android.internal.R.color.system_notification_accent_color))
469db31363aa9670dbbc5837af7a04bdd8012493fd7Felipe Leme                .setContentIntent(infoPendingIntent)
470bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                .addAction(infoAction)
471d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .addAction(screenshotAction)
472923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .addAction(cancelAction)
473923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .build();
474923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
4752288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme        if (info.finished) {
4762288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme            Log.w(TAG, "Not sending progress notification because bugreport has finished already ("
4772288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                    + info + ")");
4782288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme            return;
4792288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme        }
480262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme        if (DEBUG) {
481262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme            Log.d(TAG, "Sending 'Progress' notification for id " + info.id + "(pid " + info.pid
482262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                    + "): " + percentText);
483262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme        }
484fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        NotificationManager.from(mContext).notify(TAG, info.id, notification);
485923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
486923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
487923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
48846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Creates a {@link PendingIntent} for a notification action used to cancel a bugreport.
48946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     */
49046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    private static PendingIntent newCancelIntent(Context context, BugreportInfo info) {
49146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent intent = new Intent(INTENT_BUGREPORT_CANCEL);
49246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        intent.setClass(context, BugreportProgressService.class);
493fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        intent.putExtra(EXTRA_ID, info.id);
494fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        return PendingIntent.getService(context, info.id, intent,
495bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                PendingIntent.FLAG_UPDATE_CURRENT);
49646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    }
49746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
49846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    /**
49946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Finalizes the progress on a given bugreport and cancel its notification.
500923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
501fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void stopProgress(int id) {
502fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (mProcesses.indexOfKey(id) < 0) {
503fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "ID not watched: " + id);
504d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
505fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "Removing ID " + id);
506fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mProcesses.remove(id);
50769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
508d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        stopSelfWhenDone();
509fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "stopProgress(" + id + "): cancel notification");
510fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        NotificationManager.from(mContext).cancel(TAG, id);
511923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
512923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
513923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
514923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Cancels a bugreport upon user's request.
515923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
516fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void cancel(int id) {
5176605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_CANCEL);
518fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "cancel: ID=" + id);
519fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
520d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info != null && !info.finished) {
521fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
522d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
523d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            deleteScreenshots(info);
524bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
525fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        stopProgress(id);
526923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
5279cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme
528923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
529923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Poll {@link SystemProperties} to get the progress on each monitored process.
530923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     *
531923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * @return whether it should keep polling.
532923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
533923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private boolean pollProgress() {
534d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int total = mProcesses.size();
535d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (total == 0) {
536d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.d(TAG, "No process to poll progress.");
537d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
538d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        int activeProcesses = 0;
539d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (int i = 0; i < total; i++) {
540d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final BugreportInfo info = mProcesses.valueAt(i);
541af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            if (info == null) {
542fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                Log.wtf(TAG, "pollProgress(): null info at index " + i + "(ID = "
543af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                        + mProcesses.keyAt(i) + ")");
544af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                continue;
545af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            }
546af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme
547af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            final int pid = info.pid;
548fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            final int id = info.id;
549d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (info.finished) {
55085ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme                if (DEBUG) Log.v(TAG, "Skipping finished process " + pid + " (id: " + id + ")");
551d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                continue;
552923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            }
553d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            activeProcesses++;
554d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final String progressKey = DUMPSTATE_PREFIX + pid + PROGRESS_SUFFIX;
555d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int progress = SystemProperties.getInt(progressKey, 0);
556d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (progress == 0) {
557d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.v(TAG, "System property " + progressKey + " is not set yet");
558d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
559d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int max = SystemProperties.getInt(DUMPSTATE_PREFIX + pid + MAX_SUFFIX, 0);
560d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final boolean maxChanged = max > 0 && max != info.max;
561d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final boolean progressChanged = progress > 0 && progress != info.progress;
562d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
563d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (progressChanged || maxChanged) {
564d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (progressChanged) {
565fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + "(id: " + id
566fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                            + ") from " + info.progress + " to " + progress);
567d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    info.progress = progress;
56846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                }
569d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (maxChanged) {
570fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    Log.i(TAG, "Updating max progress for PID " + pid + "(id: " + id
571fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                            + ") from " + info.max + " to " + max);
572d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    info.max = max;
57369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                }
574d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                info.lastUpdate = System.currentTimeMillis();
575d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                updateProgress(info);
576d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            } else {
577d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                long inactiveTime = System.currentTimeMillis() - info.lastUpdate;
578d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (inactiveTime >= INACTIVITY_TIMEOUT) {
579fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    Log.w(TAG, "No progress update for PID " + pid + " since "
580d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            + info.getFormattedLastUpdate());
581fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    stopProgress(info.id);
58269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                }
58369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
58469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
585d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (DEBUG) Log.v(TAG, "pollProgress() total=" + total + ", actives=" + activeProcesses);
586d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return activeProcesses > 0;
587923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
58869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
589923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
590bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Fetches a {@link BugreportInfo} for a given process and launches a dialog where the user can
591bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * change its values.
592bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
593fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void launchBugreportInfoDialog(int id) {
5946605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_DETAILS);
595bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // Copy values so it doesn't lock mProcesses while UI is being updated
596bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String name, title, description;
597fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
598d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
5991eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // Most likely am killed Shell before user tapped the notification. Since system might
6001eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // be too busy anwyays, it's better to ignore the notification and switch back to the
6011eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // non-interactive mode (where the bugerport will be shared upon completion).
602bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme            Log.w(TAG, "launchBugreportInfoDialog(): canceling notification because id " + id
603bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                    + " was not found");
6041eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // TODO: add test case to make sure notification is canceled.
6051eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            NotificationManager.from(mContext).cancel(TAG, id);
606d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
607d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
608d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
609d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        collapseNotificationBar();
610fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        mInfoDialog.initialize(mContext, info);
611d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
612d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
613d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
614d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Starting point for taking a screenshot.
615d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
616d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * If {@code delayed} is set, it first display a toast message and waits
617d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * {@link #SCREENSHOT_DELAY_SECONDS} seconds before taking it, otherwise it takes the screenshot
618d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * right away.
619d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
620d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Typical usage is delaying when taken from the notification action, and taking it right away
621d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * upon receiving a {@link #INTENT_BUGREPORT_STARTED}.
622d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
623fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void takeScreenshot(int id, boolean delayed) {
6246605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
6251eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        if (getInfo(id) == null) {
6261eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // Most likely am killed Shell before user tapped the notification. Since system might
6271eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // be too busy anwyays, it's better to ignore the notification and switch back to the
6281eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // non-interactive mode (where the bugerport will be shared upon completion).
629bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme            Log.w(TAG, "takeScreenshot(): canceling notification because id " + id
630bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                    + " was not found");
6311eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // TODO: add test case to make sure notification is canceled.
6321eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            NotificationManager.from(mContext).cancel(TAG, id);
6331eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            return;
6341eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        }
635d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        setTakingScreenshot(true);
636d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (delayed) {
637d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            collapseNotificationBar();
638d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final String msg = mContext.getResources()
639d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    .getQuantityString(com.android.internal.R.plurals.bugreport_countdown,
640d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            SCREENSHOT_DELAY_SECONDS, SCREENSHOT_DELAY_SECONDS);
641d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, msg);
642d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // Show a toast just once, otherwise it might be captured in the screenshot.
643d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
644d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
645fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            takeScreenshot(id, SCREENSHOT_DELAY_SECONDS);
646d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
647fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            takeScreenshot(id, 0);
648d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
649d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
650d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
651d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
652d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Takes a screenshot after {@code delay} seconds.
653d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
654fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void takeScreenshot(int id, int delay) {
655d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (delay > 0) {
656fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "Taking screenshot for " + id + " in " + delay + " seconds");
657d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Message msg = mMainHandler.obtainMessage();
658d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.what = MSG_DELAYED_SCREENSHOT;
659fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            msg.arg1 = id;
660d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.arg2 = delay - 1;
661d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mMainHandler.sendMessageDelayed(msg, DateUtils.SECOND_IN_MILLIS);
662d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
663d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
664d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
665d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        // It's time to take the screenshot: let the proper thread handle it
666fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
667d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
668d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
669d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
670d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final String screenshotPath =
671d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                new File(mScreenshotsDir, info.getPathNextScreenshot()).getAbsolutePath();
672d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
6738648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme        Message.obtain(mScreenshotHandler, MSG_SCREENSHOT_REQUEST, id, UNUSED_ARG2, screenshotPath)
6748648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme                .sendToTarget();
675d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
676d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
677d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
678d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Sets the internal {@code mTakingScreenshot} state and updates all notifications so their
679d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * SCREENSHOT button is enabled or disabled accordingly.
680d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
681d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void setTakingScreenshot(boolean flag) {
682d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        synchronized (BugreportProgressService.this) {
683d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mTakingScreenshot = flag;
684d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            for (int i = 0; i < mProcesses.size(); i++) {
6852288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                final BugreportInfo info = mProcesses.valueAt(i);
6862288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                if (info.finished) {
6872288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                    Log.d(TAG, "Not updating progress because share notification was already sent");
6882288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                    continue;
6892288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                }
6902288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                updateProgress(info);
691bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
692bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
693d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
694bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
695d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void handleScreenshotRequest(Message requestMsg) {
696d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        String screenshotFile = (String) requestMsg.obj;
697d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        boolean taken = takeScreenshot(mContext, screenshotFile);
698d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        setTakingScreenshot(false);
699d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
7008648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme        Message.obtain(mMainHandler, MSG_SCREENSHOT_RESPONSE, requestMsg.arg1, taken ? 1 : 0,
7018648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme                screenshotFile).sendToTarget();
702d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
703bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
704d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void handleScreenshotResponse(Message resultMsg) {
705d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final boolean taken = resultMsg.arg2 != 0;
706d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(resultMsg.arg1);
707d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
708d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
709d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
710d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final File screenshotFile = new File((String) resultMsg.obj);
711d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
7125d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme        final String msg;
713d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (taken) {
714d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.addScreenshot(screenshotFile);
715c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            if (info.finished) {
716c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                Log.d(TAG, "Screenshot finished after bugreport; updating share notification");
717c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                info.renameScreenshots(mScreenshotsDir);
718c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                sendBugreportNotification(mContext, info);
719c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
7205d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            msg = mContext.getString(R.string.bugreport_screenshot_taken);
721d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
722d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // TODO: try again using Framework APIs instead of relying on screencap.
7235d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            msg = mContext.getString(R.string.bugreport_screenshot_failed);
7245d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
725d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
726d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Log.d(TAG, msg);
727d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
728d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
729d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
730d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Deletes all screenshots taken for a given bugreport.
731d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
732d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void deleteScreenshots(BugreportInfo info) {
733d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (File file : info.screenshotFiles) {
734d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Deleting screenshot file " + file);
735d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            file.delete();
736d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
737bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
738bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
739bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
740923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Finishes the service when it's not monitoring any more processes.
741923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
742923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void stopSelfWhenDone() {
743d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (mProcesses.size() > 0) {
744fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (DEBUG) Log.d(TAG, "Staying alive, waiting for IDs " + mProcesses);
745d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
74669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
747fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "No more processes to handle, shutting down");
748d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        stopSelf();
749923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
75069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
751bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
752bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
753bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
754fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void onBugreportFinished(int id, Intent intent) {
755af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
756af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        if (bugreportFile == null) {
757af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            // Should never happen, dumpstate always set the file.
758af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent);
759af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            return;
760af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        }
761fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        mInfoDialog.onBugreportFinished(id);
762fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo info = getInfo(id);
763d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
764d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED first.
765fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.v(TAG, "Creating info for untracked ID " + id);
766fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            info = new BugreportInfo(mContext, id);
767fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mProcesses.put(id, info);
768d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
769d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.renameScreenshots(mScreenshotsDir);
770af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        info.bugreportFile = bugreportFile;
771af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme
772510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        final int max = intent.getIntExtra(EXTRA_MAX, -1);
773510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        if (max != -1) {
774510e922e47fec69839dd48c5473540f93d79a508Felipe Leme            MetricsLogger.histogram(this, "dumpstate_duration", max);
775510e922e47fec69839dd48c5473540f93d79a508Felipe Leme            info.max = max;
776510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        }
777510e922e47fec69839dd48c5473540f93d79a508Felipe Leme
778d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final File screenshot = getFileExtra(intent, EXTRA_SCREENSHOT);
779d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (screenshot != null) {
780d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.addScreenshot(screenshot);
78146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
782d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.finished = true;
78369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
784d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Configuration conf = mContext.getResources().getConfiguration();
785923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
786d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            triggerLocalNotification(mContext, info);
787b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
788b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
789b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
790b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
79169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Responsible for triggering a notification that allows the user to start a "share" intent with
79246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * the bugreport. On watches we have other methods to allow the user to start this intent
79369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * (usually by triggering it on another connected device); we don't need to display the
79469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * notification in this case.
795b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
796d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void triggerLocalNotification(final Context context, final BugreportInfo info) {
79746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
79846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
799d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Toast.makeText(context, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
800fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            stopProgress(info.id);
801b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return;
802b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
803b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
80446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
805b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (!isPlainText) {
806b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Already zipped, send it right away.
80746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            sendBugreportNotification(context, info);
808b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
809b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Asynchronously zip the file first, then send it.
81046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            sendZippedBugreportNotification(context, info);
811b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
812b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
813b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
814b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Intent buildWarningIntent(Context context, Intent sendIntent) {
815b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Intent intent = new Intent(context, BugreportWarningActivity.class);
816b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
817b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return intent;
818b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
819b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
820b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
821b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Build {@link Intent} that can be used to share the given bugreport.
822b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
823bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static Intent buildSendIntent(Context context, BugreportInfo info) {
824bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // Files are kept on private storage, so turn into Uris that we can
825bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // grant temporary permissions for.
826bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final Uri bugreportUri = getUri(context, info.bugreportFile);
827bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
828b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
829b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final String mimeType = "application/vnd.android.bugreport";
830b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
831b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.addCategory(Intent.CATEGORY_DEFAULT);
832b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.setType(mimeType);
833b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
834c8e2b6092c0fbf87e71f81fd2cffbb29ff8d9039Felipe Leme        final String subject = !TextUtils.isEmpty(info.title) ?
835c8e2b6092c0fbf87e71f81fd2cffbb29ff8d9039Felipe Leme                info.title : bugreportUri.getLastPathSegment();
836bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
837b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
838b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
839b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
840b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // create the ClipData object with the attachments URIs.
841d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final StringBuilder messageBody = new StringBuilder("Build info: ")
842bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append(SystemProperties.get("ro.build.description"))
843bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append("\nSerial number: ")
844bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append(SystemProperties.get("ro.serialno"));
845bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        if (!TextUtils.isEmpty(info.description)) {
846bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            messageBody.append("\nDescription: ").append(info.description);
847bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
848bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
849b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final ClipData clipData = new ClipData(null, new String[] { mimeType },
850b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                new ClipData.Item(null, null, null, bugreportUri));
851b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
852d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (File screenshot : info.screenshotFiles) {
853d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Uri screenshotUri = getUri(context, screenshot);
854b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
855b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            attachments.add(screenshotUri);
856b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
857b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.setClipData(clipData);
858b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
859b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
860b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Account sendToAccount = findSendToAccount(context);
861b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (sendToAccount != null) {
862b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
863b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
864b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
865b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return intent;
866b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
867b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
868b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
86946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Shares the bugreport upon user's request by issuing a {@link Intent#ACTION_SEND_MULTIPLE}
87046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * intent, but issuing a warning dialog the first time.
871b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
872fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void shareBugreport(int id, BugreportInfo sharedInfo) {
8736605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SHARE);
874fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo info = getInfo(id);
875d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
876c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            // Service was terminated but notification persisted
877c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            info = sharedInfo;
878fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
879c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                    + mProcesses + "), using info from intent instead (" + info + ")");
88046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
8814967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
88218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        addDetailsToZipFile(mContext, info);
8834967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
884d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent sendIntent = buildSendIntent(mContext, info);
88546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent notifIntent;
886b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
887b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // Send through warning dialog by default
888d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (getWarningState(mContext, STATE_SHOW) == STATE_SHOW) {
889d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            notifIntent = buildWarningIntent(mContext, sendIntent);
890b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
891b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            notifIntent = sendIntent;
892b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
893b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
894b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
89546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        // Send the share intent...
896d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mContext.startActivity(notifIntent);
89746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
89846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        // ... and stop watching this process.
899fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        stopProgress(id);
90046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    }
90146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
90246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    /**
9032758d5d93970f26867d778c944605371e55b751eFelipe Leme     * Sends a notification indicating the bugreport has finished so use can share it.
90446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     */
90546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    private static void sendBugreportNotification(Context context, BugreportInfo info) {
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
916fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final String title = context.getString(R.string.bugreport_finished_title, info.id);
917b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Notification.Builder builder = new Notification.Builder(context)
918b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
91969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                .setContentTitle(title)
92069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                .setTicker(title)
921b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                .setContentText(context.getString(R.string.bugreport_finished_text))
922fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                .setContentIntent(PendingIntent.getService(context, info.id, shareIntent,
923bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        PendingIntent.FLAG_UPDATE_CURRENT))
92446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                .setDeleteIntent(newCancelIntent(context, info))
925b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                .setLocalOnly(true)
926b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                .setColor(context.getColor(
927b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        com.android.internal.R.color.system_notification_accent_color));
928b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
929bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        if (!TextUtils.isEmpty(info.name)) {
930bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            builder.setContentInfo(info.name);
931bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
932bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
933fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
934fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        NotificationManager.from(context).notify(TAG, info.id, builder.build());
935b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
936b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
937b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
9382758d5d93970f26867d778c944605371e55b751eFelipe Leme     * Sends a notification indicating the bugreport is being updated so the user can wait until it
9392758d5d93970f26867d778c944605371e55b751eFelipe Leme     * finishes - at this point there is nothing to be done other than waiting, hence it has no
9402758d5d93970f26867d778c944605371e55b751eFelipe Leme     * pending action.
9412758d5d93970f26867d778c944605371e55b751eFelipe Leme     */
942fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private static void sendBugreportBeingUpdatedNotification(Context context, int id) {
9432758d5d93970f26867d778c944605371e55b751eFelipe Leme        final String title = context.getString(R.string.bugreport_updating_title);
9442758d5d93970f26867d778c944605371e55b751eFelipe Leme        final Notification.Builder builder = new Notification.Builder(context)
9452758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
9462758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setContentTitle(title)
9472758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setTicker(title)
9482758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setContentText(context.getString(R.string.bugreport_updating_wait))
9492758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setLocalOnly(true)
9502758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setColor(context.getColor(
9512758d5d93970f26867d778c944605371e55b751eFelipe Leme                        com.android.internal.R.color.system_notification_accent_color));
952fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "Sending 'Updating zip' notification for ID " + id + ": " + title);
953fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        NotificationManager.from(context).notify(TAG, id, builder.build());
9542758d5d93970f26867d778c944605371e55b751eFelipe Leme    }
9552758d5d93970f26867d778c944605371e55b751eFelipe Leme
9562758d5d93970f26867d778c944605371e55b751eFelipe Leme    /**
957b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Sends a zipped bugreport notification.
958b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
959b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static void sendZippedBugreportNotification(final Context context,
96046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final BugreportInfo info) {
961b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        new AsyncTask<Void, Void, Void>() {
962b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            @Override
963b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            protected Void doInBackground(Void... params) {
9644967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                zipBugreport(info);
96546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                sendBugreportNotification(context, info);
966b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                return null;
967b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
968b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }.execute();
969b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
970b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
971b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
972b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Zips a bugreport file, returning the path to the new file (or to the
973b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * original in case of failure).
974b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
9754967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void zipBugreport(BugreportInfo info) {
9764967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final String bugreportPath = info.bugreportFile.getAbsolutePath();
9774967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final String zippedPath = bugreportPath.replace(".txt", ".zip");
978b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
9794967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File bugreportZippedFile = new File(zippedPath);
9804967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        try (InputStream is = new FileInputStream(info.bugreportFile);
98169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                ZipOutputStream zos = new ZipOutputStream(
98269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
9834967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, info.bugreportFile.getName(), is);
9844967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // Delete old file
9854967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            final boolean deleted = info.bugreportFile.delete();
986b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            if (deleted) {
987b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
988b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            } else {
989b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
990b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
9914967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            info.bugreportFile = bugreportZippedFile;
992b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } catch (IOException e) {
99369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            Log.e(TAG, "exception zipping file " + zippedPath, e);
994b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
995b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
996b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
997b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
9984967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * Adds the user-provided info into the bugreport zip file.
9994967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * <p>
10004967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * If user provided a title, it will be saved into a {@code title.txt} entry; similarly, the
10014967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * description will be saved on {@code description.txt}.
10024967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     */
100318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme    private static void addDetailsToZipFile(Context context, BugreportInfo info) {
1004c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        if (info.bugreportFile == null) {
1005c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            // One possible reason is a bug in the Parcelization code.
1006af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
1007c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return;
1008c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1009b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme        if (TextUtils.isEmpty(info.title) && TextUtils.isEmpty(info.description)) {
1010b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme            Log.d(TAG, "Not touching zip file since neither title nor description are set");
1011b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme            return;
1012b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme        }
101318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        if (info.addedDetailsToZip || info.addingDetailsToZip) {
101418b5892950b7f21e66c9268129323cbc0e865699Felipe Leme            Log.d(TAG, "Already added details to zip file for " + info);
101518b5892950b7f21e66c9268129323cbc0e865699Felipe Leme            return;
101618b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        }
101718b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        info.addingDetailsToZip = true;
10182758d5d93970f26867d778c944605371e55b751eFelipe Leme
10194967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        // It's not possible to add a new entry into an existing file, so we need to create a new
10204967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        // zip, copy all entries, then rename it.
1021fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        sendBugreportBeingUpdatedNotification(context, info.id); // ...and that takes time
10224967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File dir = info.bugreportFile.getParentFile();
10234967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
10242758d5d93970f26867d778c944605371e55b751eFelipe Leme        Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
10254967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        try (ZipFile oldZip = new ZipFile(info.bugreportFile);
10264967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
10274967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10284967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // First copy contents from original zip.
10294967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Enumeration<? extends ZipEntry> entries = oldZip.entries();
10304967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            while (entries.hasMoreElements()) {
10314967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                final ZipEntry entry = entries.nextElement();
10324967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                final String entryName = entry.getName();
10334967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                if (!entry.isDirectory()) {
10344967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                    addEntry(zos, entryName, entry.getTime(), oldZip.getInputStream(entry));
10354967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                } else {
10364967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                    Log.w(TAG, "skipping directory entry: " + entryName);
10374967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                }
10384967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            }
10394967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10404967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // Then add the user-provided info.
10414967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, "title.txt", info.title);
10424967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, "description.txt", info.description);
10434967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        } catch (IOException e) {
104418b5892950b7f21e66c9268129323cbc0e865699Felipe Leme            info.addingDetailsToZip = false;
10454967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Log.e(TAG, "exception zipping file " + tmpZip, e);
10464967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            return;
10474967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
10484967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10494967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (!tmpZip.renameTo(info.bugreportFile)) {
10504967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
10514967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
105218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        info.addedDetailsToZip = true;
105318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        info.addingDetailsToZip = false;
10544967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
10554967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10564967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entry, String text)
10574967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            throws IOException {
10584967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (DEBUG) Log.v(TAG, "adding entry '" + entry + "': " + text);
10594967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (!TextUtils.isEmpty(text)) {
10604967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, entry, new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)));
10614967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
10624967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
10634967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10644967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entryName, InputStream is)
10654967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            throws IOException {
10664967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        addEntry(zos, entryName, System.currentTimeMillis(), is);
10674967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
10684967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10694967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entryName, long timestamp,
10704967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            InputStream is) throws IOException {
10714967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final ZipEntry entry = new ZipEntry(entryName);
10724967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        entry.setTime(timestamp);
10734967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        zos.putNextEntry(entry);
10744967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final int totalBytes = Streams.copy(is, zos);
10754967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (DEBUG) Log.v(TAG, "size of '" + entryName + "' entry: " + totalBytes + " bytes");
10764967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        zos.closeEntry();
10774967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
10784967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
10794967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    /**
1080b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Find the best matching {@link Account} based on build properties.
1081b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
1082b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Account findSendToAccount(Context context) {
1083b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final AccountManager am = (AccountManager) context.getSystemService(
1084b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Context.ACCOUNT_SERVICE);
1085b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1086b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
1087b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (!preferredDomain.startsWith("@")) {
1088b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            preferredDomain = "@" + preferredDomain;
1089b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1090b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1091b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Account[] accounts = am.getAccounts();
1092b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        Account foundAccount = null;
1093b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        for (Account account : accounts) {
1094b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
1095b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                if (!preferredDomain.isEmpty()) {
1096b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // if we have a preferred domain and it matches, return; otherwise keep
1097b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // looking
1098b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    if (account.name.endsWith(preferredDomain)) {
1099b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        return account;
1100b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    } else {
1101b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        foundAccount = account;
1102b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    }
1103b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // if we don't have a preferred domain, just return since it looks like
1104b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // an email address
1105b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                } else {
1106b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    return account;
1107b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                }
1108b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
1109b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1110b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return foundAccount;
1111b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1112b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1113226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski    static Uri getUri(Context context, File file) {
1114b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
1115b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1116b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1117b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static File getFileExtra(Intent intent, String key) {
1118b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final String path = intent.getStringExtra(key);
1119b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (path != null) {
1120b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return new File(path);
1121b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
1122b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return null;
1123b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1124b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
112569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
1126bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static boolean setSystemProperty(String key, String value) {
1127bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        try {
112885ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            if (DEBUG) Log.v(TAG, "Setting system property " + key + " to " + value);
1129bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            SystemProperties.set(key, value);
1130bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        } catch (IllegalArgumentException e) {
1131bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            Log.e(TAG, "Could not set property " + key + " to " + value, e);
1132bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            return false;
1133bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1134bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return true;
1135bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1136bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1137bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1138bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
1139bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1140bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private boolean setBugreportNameProperty(int pid, String name) {
1141bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        Log.d(TAG, "Updating bugreport name to " + name);
1142bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
1143bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return setSystemProperty(key, name);
1144bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1145bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1146bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1147bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Updates the user-provided details of a bugreport.
1148bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1149fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void updateBugreportInfo(int id, String name, String title, String description) {
1150fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
1151d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
1152d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
1153d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
11546605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        if (title != null && !title.equals(info.title)) {
11556605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_TITLE_CHANGED);
11566605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        }
1157d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.title = title;
11586605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        if (description != null && !description.equals(info.description)) {
11596605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_DESCRIPTION_CHANGED);
11606605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        }
1161d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.description = description;
11621eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        if (name != null && !name.equals(info.name)) {
11636605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_NAME_CHANGED);
1164d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.name = name;
1165d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            updateProgress(info);
1166d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1167d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1168d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1169d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void collapseNotificationBar() {
1170d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
1171d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1172d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1173d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static Looper newLooper(String name) {
1174d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final HandlerThread thread = new HandlerThread(name, THREAD_PRIORITY_BACKGROUND);
1175d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        thread.start();
1176d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return thread.getLooper();
1177d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1178d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1179d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
1180d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Takes a screenshot and save it to the given location.
1181d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
1182d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static boolean takeScreenshot(Context context, String screenshotFile) {
1183d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final ProcessBuilder screencap = new ProcessBuilder()
1184d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                .command("/system/bin/screencap", "-p", screenshotFile);
1185d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Log.d(TAG, "Taking screenshot using " + screencap.command());
1186d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        try {
1187d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final int exitValue = screencap.start().waitFor();
1188d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (exitValue == 0) {
1189aa00f2d909dcc48b61b9338cd2ab7c33850a69d9Felipe Leme                ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)).vibrate(150);
1190d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return true;
1191bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1192d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.e(TAG, "screencap (" + screencap.command() + ") failed: " + exitValue);
1193d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } catch (IOException e) {
1194d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.e(TAG, "screencap (" + screencap.command() + ") failed", e);
1195d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } catch (InterruptedException e) {
1196d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.w(TAG, "Thread interrupted while screencap still running");
1197d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Thread.currentThread().interrupt();
1198bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1199d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return false;
1200bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1201bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1202bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1203bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Checks whether a character is valid on bugreport names.
1204bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1205bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    @VisibleForTesting
1206bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static boolean isValid(char c) {
1207bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
1208bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                || c == '_' || c == '-';
1209bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1210bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1211bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1212bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Helper class encapsulating the UI elements and logic used to display a dialog where user
1213bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * can change the details of a bugreport.
1214bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1215bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private final class BugreportInfoDialog {
1216bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoName;
1217bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoTitle;
1218bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoDescription;
1219bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private AlertDialog mDialog;
1220bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private Button mOkButton;
1221fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        private int mId;
1222bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private int mPid;
1223bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1224bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1225bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Last "committed" value of the bugreport name.
1226bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1227bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Once initially set, it's only updated when user clicks the OK button.
1228bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1229bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private String mSavedName;
1230bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1231bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1232bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Last value of the bugreport name as entered by the user.
1233bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1234bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Every time it's changed the equivalent system property is changed as well, but if the
1235bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
1236bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1237bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
1238bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * user changed the name but didn't clicked OK yet (for example, because the user is typing
1239bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * the description). The only drawback is that if the user changes the name while
1240bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
1241bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
1242bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
1243bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * such drawback.
1244bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1245bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private String mTempName;
1246bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1247bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1248bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Sets its internal state and displays the dialog.
1249bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
12506605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        private void initialize(final Context context, BugreportInfo info) {
1251262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme            final String dialogTitle =
1252262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                    context.getString(R.string.bugreport_info_dialog_title, info.id);
1253bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // First initializes singleton.
1254bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mDialog == null) {
1255bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                @SuppressLint("InflateParams")
1256bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                // It's ok pass null ViewRoot on AlertDialogs.
1257bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                final View view = View.inflate(context, R.layout.dialog_bugreport_info, null);
1258bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1259bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName = (EditText) view.findViewById(R.id.name);
1260bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoTitle = (EditText) view.findViewById(R.id.title);
1261bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoDescription = (EditText) view.findViewById(R.id.description);
1262bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1263bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
1264bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1265bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    @Override
1266bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    public void onFocusChange(View v, boolean hasFocus) {
1267bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        if (hasFocus) {
1268bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                            return;
1269bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        }
1270bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                        // Select-all is useful just initially, since the date-based filename is
1271bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                        // full of hyphens.
1272bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                        mInfoName.setSelectAllOnFocus(false);
1273bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        sanitizeName();
1274bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    }
1275bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                });
1276bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1277bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mDialog = new AlertDialog.Builder(context)
1278bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setView(view)
1279262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                        .setTitle(dialogTitle)
1280bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setCancelable(false)
1281bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                        .setPositiveButton(context.getString(R.string.save),
1282bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                null)
1283bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setNegativeButton(context.getString(com.android.internal.R.string.cancel),
1284bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                new DialogInterface.OnClickListener()
1285bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                {
1286bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    @Override
1287bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    public void onClick(DialogInterface dialog, int id)
1288bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    {
12896605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                                        MetricsLogger.action(context,
12906605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                                                MetricsEvent.ACTION_BUGREPORT_DETAILS_CANCELED);
1291bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                        if (!mTempName.equals(mSavedName)) {
1292bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            // Must restore dumpstate's name since it was changed
1293bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            // before user clicked OK.
1294bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            setBugreportNameProperty(mPid, mSavedName);
1295bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                        }
1296bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    }
1297bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                })
1298bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .create();
1299bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1300bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mDialog.getWindow().setAttributes(
1301bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        new WindowManager.LayoutParams(
1302bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG));
1303bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1304262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme            } else {
1305262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                // Re-use view, but reset fields first.
1306262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mDialog.setTitle(dialogTitle);
1307262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoName.setText(null);
1308262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoTitle.setText(null);
1309262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoDescription.setText(null);
1310bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1311bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1312bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // Then set fields.
1313fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mSavedName = mTempName = info.name;
1314fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mId = info.id;
1315fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mPid = info.pid;
1316fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.name)) {
1317fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoName.setText(info.name);
1318bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1319fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.title)) {
1320fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoTitle.setText(info.title);
1321bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1322fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.description)) {
1323fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoDescription.setText(info.description);
1324bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1325bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1326bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // And finally display it.
1327bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mDialog.show();
1328bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1329bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // TODO: in a traditional AlertDialog, when the positive button is clicked the
1330bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // dialog is always closed, but we need to validate the name first, so we need to
1331bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // get a reference to it, which is only available after it's displayed.
1332bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // It would be cleaner to use a regular dialog instead, but let's keep this
1333bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // workaround for now and change it later, when we add another button to take
1334bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // extra screenshots.
1335bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mOkButton == null) {
1336bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mOkButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
1337bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mOkButton.setOnClickListener(new View.OnClickListener() {
1338bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1339bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    @Override
1340bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    public void onClick(View view) {
13416605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                        MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
1342bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        sanitizeName();
1343bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String name = mInfoName.getText().toString();
1344bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String title = mInfoTitle.getText().toString();
1345bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String description = mInfoDescription.getText().toString();
1346bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1347fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        updateBugreportInfo(mId, name, title, description);
1348bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        mDialog.dismiss();
1349bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    }
1350bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                });
1351bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1352bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1353bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1354bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1355bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Sanitizes the user-provided value for the {@code name} field, automatically replacing
1356bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * invalid characters if necessary.
1357bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1358d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        private void sanitizeName() {
1359bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            String name = mInfoName.getText().toString();
1360bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (name.equals(mTempName)) {
1361bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
1362bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                return;
1363bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1364bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            final StringBuilder safeName = new StringBuilder(name.length());
1365bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            boolean changed = false;
1366bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            for (int i = 0; i < name.length(); i++) {
1367bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                final char c = name.charAt(i);
1368bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                if (isValid(c)) {
1369bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    safeName.append(c);
1370bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                } else {
1371bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    changed = true;
1372bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    safeName.append('_');
1373bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                }
1374bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1375bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (changed) {
1376bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                Log.v(TAG, "changed invalid name '" + name + "' to '" + safeName + "'");
1377bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                name = safeName.toString();
1378bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(name);
1379bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1380bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mTempName = name;
1381bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1382bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // Must update system property for the cases where dumpstate finishes
1383bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // while the user is still entering other fields (like title or
1384bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // description)
138585ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            setBugreportNameProperty(mPid, name);
1386bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1387bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1388bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme       /**
1389bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Notifies the dialog that the bugreport has finished so it disables the {@code name}
1390bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * field.
1391bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>Once the bugreport is finished dumpstate has already generated the final files, so
1392bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * changing the name would have no effect.
1393bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1394fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        private void onBugreportFinished(int id) {
1395bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mInfoName != null) {
1396bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setEnabled(false);
1397bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(mSavedName);
1398bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1399bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1400bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1401bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1402bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
140369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /**
140446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Information about a bugreport process while its in progress.
140569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     */
1406c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    private static final class BugreportInfo implements Parcelable {
1407719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        private final Context context;
1408719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme
140969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
1410fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme         * Sequential, user-friendly id used to identify the bugreport.
1411fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme         */
1412fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final int id;
1413fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme
1414fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        /**
141546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * {@code pid} of the {@code dumpstate} process generating the bugreport.
141669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
141769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        final int pid;
141869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
141969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
142046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Name of the bugreport, will be used to rename the final files.
142169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * <p>
142246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Initial value is the bugreport filename reported by {@code dumpstate}, but user can
142369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * change it later to a more meaningful name.
142469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
1425719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        String name;
142669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
142769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
1428bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * User-provided, one-line summary of the bug; when set, will be used as the subject
1429bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
1430bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1431bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        String title;
1432bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1433bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1434bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * User-provided, detailed description of the bugreport; when set, will be added to the body
1435bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
1436bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1437bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        String description;
1438bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1439bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
144046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Maximum progress of the bugreport generation.
144169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
1442719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        int max;
144369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
144469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
144546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Current progress of the bugreport generation.
144669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
144769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        int progress;
144869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
144969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
145069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * Time of the last progress update.
145169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
145269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        long lastUpdate = System.currentTimeMillis();
145369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
145446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
1455c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme         * Time of the last progress update when Parcel was created.
1456c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme         */
1457c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        String formattedLastUpdate;
1458c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1459c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        /**
146046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Path of the main bugreport file.
146146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
146246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        File bugreportFile;
146346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
146446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
1465d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Path of the screenshot files.
146646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1467d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        List<File> screenshotFiles = new ArrayList<>(1);
146846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
146946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
147046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Whether dumpstate sent an intent informing it has finished.
147146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
147246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        boolean finished;
147346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
147446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
147518b5892950b7f21e66c9268129323cbc0e865699Felipe Leme         * Whether the details entries have been added to the bugreport yet.
147618b5892950b7f21e66c9268129323cbc0e865699Felipe Leme         */
147718b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        boolean addingDetailsToZip;
147818b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        boolean addedDetailsToZip;
147918b5892950b7f21e66c9268129323cbc0e865699Felipe Leme
148018b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        /**
1481d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Internal counter used to name screenshot files.
1482d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1483d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        int screenshotCounter;
1484d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1485d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
148646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
148746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1488fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo(Context context, int id, int pid, String name, int max) {
1489719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme            this.context = context;
1490fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            this.id = id;
149169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.pid = pid;
149269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.name = name;
149369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.max = max;
149469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
149569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
149646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
149746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
149846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * without a previous call to BUGREPORT_STARTED.
149946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1500fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo(Context context, int id) {
1501fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            this(context, id, id, null, 0);
150246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            this.finished = true;
150346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
150446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
1505d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1506d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Gets the name for next screenshot file.
1507d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1508d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        String getPathNextScreenshot() {
1509d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotCounter ++;
1510d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return "screenshot-" + pid + "-" + screenshotCounter + ".png";
1511d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1512d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1513d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1514d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Saves the location of a taken screenshot so it can be sent out at the end.
1515d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1516d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        void addScreenshot(File screenshot) {
1517d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotFiles.add(screenshot);
1518d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1519d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1520d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1521d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Rename all screenshots files so that they contain the user-generated name instead of pid.
1522d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1523d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        void renameScreenshots(File screenshotDir) {
1524d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (TextUtils.isEmpty(name)) {
1525d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
1526d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
1527d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
1528d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            for (File oldFile : screenshotFiles) {
1529d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final String oldName = oldFile.getName();
153085ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme                final String newName = oldName.replaceFirst(Integer.toString(pid), name);
1531d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final File newFile;
1532d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (!newName.equals(oldName)) {
1533d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    final File renamedFile = new File(screenshotDir, newName);
1534d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
1535d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                } else {
1536d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
1537d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    newFile = oldFile;
1538d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                }
1539d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                renamedFiles.add(newFile);
1540d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
1541d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotFiles = renamedFiles;
1542d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1543d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
154469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        String getFormattedLastUpdate() {
1545c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            if (context == null) {
1546c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                // Restored from Parcel
1547c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return formattedLastUpdate == null ?
1548c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                        Long.toString(lastUpdate) : formattedLastUpdate;
1549c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1550719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme            return DateUtils.formatDateTime(context, lastUpdate,
1551719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
155269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
155369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
155469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        @Override
155569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        public String toString() {
155669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final float percent = ((float) progress * 100 / max);
1557fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            return "id: " + id + ", pid: " + pid + ", name: " + name + ", finished: " + finished
1558bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    + "\n\ttitle: " + title + "\n\tdescription: " + description
1559d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    + "\n\tfile: " + bugreportFile + "\n\tscreenshots: " + screenshotFiles
1560510e922e47fec69839dd48c5473540f93d79a508Felipe Leme                    + "\n\tprogress: " + progress + "/" + max + " (" + percent + ")"
156118b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + "\n\tlast_update: " + getFormattedLastUpdate()
156218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + "\naddingDetailsToZip: " + addingDetailsToZip
156318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + " addedDetailsToZip: " + addedDetailsToZip;
156469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
1565c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1566c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        // Parcelable contract
1567c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        protected BugreportInfo(Parcel in) {
1568c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            context = null;
1569fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            id = in.readInt();
1570c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            pid = in.readInt();
1571c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            name = in.readString();
1572c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            title = in.readString();
1573c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            description = in.readString();
1574c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            max = in.readInt();
1575c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            progress = in.readInt();
1576c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            lastUpdate = in.readLong();
1577c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            formattedLastUpdate = in.readString();
1578c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            bugreportFile = readFile(in);
1579c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1580c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            int screenshotSize = in.readInt();
1581c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            for (int i = 1; i <= screenshotSize; i++) {
1582c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                  screenshotFiles.add(readFile(in));
1583c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1584c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1585c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            finished = in.readInt() == 1;
1586c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            screenshotCounter = in.readInt();
1587c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1588c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1589c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        @Override
1590c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public void writeToParcel(Parcel dest, int flags) {
1591fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            dest.writeInt(id);
1592c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(pid);
1593c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(name);
1594c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(title);
1595c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(description);
1596c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(max);
1597c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(progress);
1598c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeLong(lastUpdate);
1599c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(getFormattedLastUpdate());
1600c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            writeFile(dest, bugreportFile);
1601c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1602c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(screenshotFiles.size());
1603c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            for (File screenshotFile : screenshotFiles) {
1604c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                writeFile(dest, screenshotFile);
1605c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1606c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1607c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(finished ? 1 : 0);
1608c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(screenshotCounter);
1609c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1610c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1611c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        @Override
1612c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public int describeContents() {
1613c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return 0;
1614c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1615c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1616c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        private void writeFile(Parcel dest, File file) {
1617c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(file == null ? null : file.getPath());
1618c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1619c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1620c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        private File readFile(Parcel in) {
1621c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            final String path = in.readString();
1622c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return path == null ? null : new File(path);
1623c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1624c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1625c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public static final Parcelable.Creator<BugreportInfo> CREATOR =
1626c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                new Parcelable.Creator<BugreportInfo>() {
1627c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            public BugreportInfo createFromParcel(Parcel source) {
1628c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return new BugreportInfo(source);
1629c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1630c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1631c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            public BugreportInfo[] newArray(int size) {
1632c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return new BugreportInfo[size];
1633c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1634c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        };
1635c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
163669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
1637b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme}
1638