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;
20fcca68dfb137c061952d23e1873e995e6bcf172dFelipe Lemeimport static com.android.shell.BugreportPrefs.STATE_HIDE;
21fcca68dfb137c061952d23e1873e995e6bcf172dFelipe Lemeimport static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
22b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport static com.android.shell.BugreportPrefs.getWarningState;
23b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
24b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.BufferedOutputStream;
254967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Lemeimport java.io.ByteArrayInputStream;
26b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.File;
2769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.io.FileDescriptor;
28b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.FileInputStream;
29b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.FileOutputStream;
30b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.IOException;
31b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.io.InputStream;
3269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.io.PrintWriter;
334967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Lemeimport java.nio.charset.StandardCharsets;
3469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport java.text.NumberFormat;
35b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.ArrayList;
364967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Lemeimport java.util.Enumeration;
37d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport java.util.List;
38b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.zip.ZipEntry;
394967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Lemeimport java.util.zip.ZipFile;
40b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport java.util.zip.ZipOutputStream;
41b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
42b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport libcore.io.Streams;
43b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
44bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport com.android.internal.annotations.VisibleForTesting;
456605bd89c53494b59717a826f9a17641bc32da41Felipe Lemeimport com.android.internal.logging.MetricsLogger;
466605bd89c53494b59717a826f9a17641bc32da41Felipe Lemeimport com.android.internal.logging.MetricsProto.MetricsEvent;
47b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport com.google.android.collect.Lists;
48b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
49b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.accounts.Account;
50b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.accounts.AccountManager;
51bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.annotation.SuppressLint;
52bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.app.AlertDialog;
53b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.Notification;
549cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Lemeimport android.app.Notification.Action;
55b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.NotificationManager;
56b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.PendingIntent;
57b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.app.Service;
58b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.ClipData;
59b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.Context;
60bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.content.DialogInterface;
61b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.Intent;
62b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.content.res.Configuration;
63aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Lemeimport android.graphics.Bitmap;
64aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Lemeimport android.hardware.display.DisplayManagerGlobal;
65b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.net.Uri;
66b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.AsyncTask;
6765a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Lemeimport android.os.Bundle;
6869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Handler;
6969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.HandlerThread;
70b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.IBinder;
7169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Looper;
7269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Message;
73c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Lemeimport android.os.Parcel;
7469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.os.Parcelable;
75b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.os.SystemProperties;
76d1e0f12979441733753b538611f6d73e5527c43cFelipe Lemeimport android.os.Vibrator;
77b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.support.v4.content.FileProvider;
78bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.text.TextUtils;
7969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.text.format.DateUtils;
80b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.util.Log;
81b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.util.Patterns;
8269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Lemeimport android.util.SparseArray;
83aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Lemeimport android.view.Display;
84aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Lemeimport android.view.KeyEvent;
85bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.View;
86bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.WindowManager;
87bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.View.OnFocusChangeListener;
88bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.view.inputmethod.EditorInfo;
89bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.widget.Button;
90bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Lemeimport android.widget.EditText;
91b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemeimport android.widget.Toast;
92b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
9369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme/**
9446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme * Service used to keep progress of bugreport processes ({@code dumpstate}).
9569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <p>
9669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * The workflow is:
9769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
98fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with a sequential id,
99fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme * its pid, and the estimated total effort.
10069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
10169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Upon start, this service:
10269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
10369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
10469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
10569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>If the progress changed, it updates the system notification.
10669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
10769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>As {@code dumpstate} progresses, it updates the system property.
10869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
10969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
11069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * turn:
11169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <ol>
11246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme * <li>Updates the system notification so user can share the bugreport.
11369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Stops monitoring that {@code dumpstate} process.
11469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * <li>Stops itself if it doesn't have any process left to monitor.
11569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
11669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme * </ol>
11769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme */
118b9238b37838d653c38ce4e712421adb61978fc22Felipe Lemepublic class BugreportProgressService extends Service {
119c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    private static final String TAG = "BugreportProgressService";
12069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final boolean DEBUG = false;
121b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
122b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static final String AUTHORITY = "com.android.shell";
123b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
12446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    // External intents sent by dumpstate.
12569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
12669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";
127226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski    static final String INTENT_REMOTE_BUGREPORT_FINISHED =
128226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski            "android.intent.action.REMOTE_BUGREPORT_FINISHED";
12946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
13046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    // Internal intents used on notification actions.
1319cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme    static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
13246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
133bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String INTENT_BUGREPORT_INFO_LAUNCH =
134bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            "android.intent.action.BUGREPORT_INFO_LAUNCH";
135d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    static final String INTENT_BUGREPORT_SCREENSHOT =
136d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            "android.intent.action.BUGREPORT_SCREENSHOT";
13769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
138b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
139b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
140fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    static final String EXTRA_ID = "android.intent.extra.ID";
14169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_PID = "android.intent.extra.PID";
14269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_MAX = "android.intent.extra.MAX";
14369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_NAME = "android.intent.extra.NAME";
144bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String EXTRA_TITLE = "android.intent.extra.TITLE";
145bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
14669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
147c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    static final String EXTRA_INFO = "android.intent.extra.INFO";
14869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
14969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final int MSG_SERVICE_COMMAND = 1;
15069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private static final int MSG_POLL = 2;
151d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_DELAYED_SCREENSHOT = 3;
152d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_SCREENSHOT_REQUEST = 4;
153d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final int MSG_SCREENSHOT_RESPONSE = 5;
154d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1558648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme    // Passed to Message.obtain() when msg.arg2 is not used.
1568648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme    private static final int UNUSED_ARG2 = -2;
1578648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme
1583fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme    // Maximum progress displayed (like 99.00%).
1593fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme    private static final int CAPPED_PROGRESS = 9900;
1603fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme    private static final int CAPPED_MAX = 10000;
1613fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme
162d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
163d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Delay before a screenshot is taken.
164d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
165d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Should be at least 3 seconds, otherwise its toast might show up in the screenshot.
166d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
167d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    static final int SCREENSHOT_DELAY_SECONDS = 3;
16869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
16969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** Polling frequency, in milliseconds. */
170bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static final long POLLING_FREQUENCY = 2 * DateUtils.SECOND_IN_MILLIS;
17169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
17269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
1731eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme    private static final long INACTIVITY_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
17469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
175719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    /** System properties used for monitoring progress. */
176719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String DUMPSTATE_PREFIX = "dumpstate.";
177719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String PROGRESS_SUFFIX = ".progress";
178719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String MAX_SUFFIX = ".max";
179bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static final String NAME_SUFFIX = ".name";
1809cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme
181bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /** System property (and value) used to stop dumpstate. */
182d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    // TODO: should call ActiveManager API instead
183719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme    private static final String CTL_STOP = "ctl.stop";
1844cc863338d5e43b6189e05498d7cb53ebba135e1Felipe Leme    private static final String BUGREPORT_SERVICE = "bugreportplus";
18569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
186d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
187d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Directory on Shell's data storage where screenshots will be stored.
188d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
189d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Must be a path supported by its FileProvider.
190d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
191d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static final String SCREENSHOT_DIR = "bugreports";
192d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
193fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    /** Managed dumpstate processes (keyed by id) */
19469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>();
19569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
196d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private Context mContext;
197d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private ServiceHandler mMainHandler;
198d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private ScreenshotHandler mScreenshotHandler;
19969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
200bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private final BugreportInfoDialog mInfoDialog = new BugreportInfoDialog();
201bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
202d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private File mScreenshotsDir;
203d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
204d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
20569c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme     * id of the notification used to set service on foreground.
20669c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme     */
20769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    private int mForegroundId = -1;
20869c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme
20969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    /**
210d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Flag indicating whether a screenshot is being taken.
211d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
212d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * This is the only state that is shared between the 2 handlers and hence must have synchronized
213d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * access.
214d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
215d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private boolean mTakingScreenshot;
216d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
21765a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Leme    private static final Bundle sNotificationBundle = new Bundle();
21865a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Leme
2199f3554176019543e654be7dba5410de2bbe3b55fWei Liu    private boolean mIsWatch;
2209f3554176019543e654be7dba5410de2bbe3b55fWei Liu
22169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
22269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void onCreate() {
223d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mContext = getApplicationContext();
224d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler = new ServiceHandler("BugreportProgressServiceMainThread");
225d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler = new ScreenshotHandler("BugreportProgressServiceScreenshotThread");
226d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
2274f663f6c25488a83a438a01dfef8372aa4e6b2aaFelipe Leme        mScreenshotsDir = new File(getFilesDir(), SCREENSHOT_DIR);
228d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (!mScreenshotsDir.exists()) {
229d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Creating directory " + mScreenshotsDir + " to store temporary screenshots");
230d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (!mScreenshotsDir.mkdir()) {
231d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.w(TAG, "Could not create directory " + mScreenshotsDir);
232d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
233d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
2349f3554176019543e654be7dba5410de2bbe3b55fWei Liu        final Configuration conf = mContext.getResources().getConfiguration();
2359f3554176019543e654be7dba5410de2bbe3b55fWei Liu        mIsWatch = (conf.uiMode & Configuration.UI_MODE_TYPE_MASK) ==
2369f3554176019543e654be7dba5410de2bbe3b55fWei Liu                Configuration.UI_MODE_TYPE_WATCH;
23769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
238b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
239b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    @Override
240b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    public int onStartCommand(Intent intent, int flags, int startId) {
241abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        Log.v(TAG, "onStartCommand(): " + dumpIntent(intent));
242b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (intent != null) {
24369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            // Handle it in a separate thread.
244d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Message msg = mMainHandler.obtainMessage();
24569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            msg.what = MSG_SERVICE_COMMAND;
24669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            msg.obj = intent;
247d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mMainHandler.sendMessage(msg);
248b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
24969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
25069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        // If service is killed it cannot be recreated because it would not know which
251fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        // dumpstate IDs it would have to watch.
252b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return START_NOT_STICKY;
253b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
254b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
255b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    @Override
256b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    public IBinder onBind(Intent intent) {
257b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return null;
258b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
259b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
26069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
26169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    public void onDestroy() {
262d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mMainHandler.getLooper().quit();
263d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mScreenshotHandler.getLooper().quit();
26469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        super.onDestroy();
26569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
266b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
26769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    @Override
26869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
269d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int size = mProcesses.size();
270d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (size == 0) {
271d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            writer.printf("No monitored processes");
272d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
273d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
27469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        writer.printf("Foreground id: %d\n\n", mForegroundId);
275d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        writer.printf("Monitored dumpstate processes\n");
276d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        writer.printf("-----------------------------\n");
277d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (int i = 0; i < size; i++) {
278d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            writer.printf("%s\n", mProcesses.valueAt(i));
27969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
28069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
28169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
282d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
283d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Main thread used to handle all requests but taking screenshots.
284d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
28569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    private final class ServiceHandler extends Handler {
286d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public ServiceHandler(String name) {
287d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            super(newLooper(name));
28869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
28969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
29069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        @Override
29169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        public void handleMessage(Message msg) {
29269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (msg.what == MSG_POLL) {
293923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                poll();
29469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
29569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
29669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
297d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what == MSG_DELAYED_SCREENSHOT) {
298d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                takeScreenshot(msg.arg1, msg.arg2);
299d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
300d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
301d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
302d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what == MSG_SCREENSHOT_RESPONSE) {
303d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                handleScreenshotResponse(msg);
304d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
305d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
306d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
30769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (msg.what != MSG_SERVICE_COMMAND) {
30869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                // Sanity check.
30969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                Log.e(TAG, "Invalid message type: " + msg.what);
31069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
31169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
31269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
31346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            // At this point it's handling onStartCommand(), with the intent passed as an Extra.
31469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            if (!(msg.obj instanceof Intent)) {
31569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                // Sanity check.
316af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                Log.wtf(TAG, "handleMessage(): invalid msg.obj type: " + msg.obj);
31769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                return;
31869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
31969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
320abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            Log.v(TAG, "handleMessage(): " + dumpIntent((Intent) parcel));
32146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final Intent intent;
32246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            if (parcel instanceof Intent) {
32346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                // The real intent was passed to BugreportReceiver, which delegated to the service.
32446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                intent = (Intent) parcel;
32546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            } else {
32646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                intent = (Intent) msg.obj;
32769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
32869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final String action = intent.getAction();
32946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final int pid = intent.getIntExtra(EXTRA_PID, 0);
33085ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            final int id = intent.getIntExtra(EXTRA_ID, 0);
33146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final int max = intent.getIntExtra(EXTRA_MAX, -1);
33246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            final String name = intent.getStringExtra(EXTRA_NAME);
33369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
334fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (DEBUG)
335fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id + ", pid: "
336fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        + pid + ", max: " + max);
33769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            switch (action) {
33869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                case INTENT_BUGREPORT_STARTED:
339fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    if (!startProgress(name, id, pid, max)) {
34069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        stopSelfWhenDone();
34169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        return;
34269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    }
34346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    poll();
34469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    break;
34569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                case INTENT_BUGREPORT_FINISHED:
346fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    if (id == 0) {
34769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
34869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        // out-of-sync dumpstate process.
349fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        Log.w(TAG, "Missing " + EXTRA_ID + " on intent " + intent);
35069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    }
351fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    onBugreportFinished(id, intent);
35246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                    break;
353bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                case INTENT_BUGREPORT_INFO_LAUNCH:
354fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    launchBugreportInfoDialog(id);
355bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    break;
356d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                case INTENT_BUGREPORT_SCREENSHOT:
357079f89614c49364bb907783b008827fbc306dd73Felipe Leme                    takeScreenshot(id);
358d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    break;
35946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                case INTENT_BUGREPORT_SHARE:
360fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    shareBugreport(id, (BugreportInfo) intent.getParcelableExtra(EXTRA_INFO));
36169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    break;
3629cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                case INTENT_BUGREPORT_CANCEL:
363fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    cancel(id);
3649cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme                    break;
36569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                default:
36669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                    Log.w(TAG, "Unsupported intent: " + action);
36769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
36869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            return;
36969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
37069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
37169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
372923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        private void poll() {
373923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            if (pollProgress()) {
374923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                // Keep polling...
375923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
37646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            } else {
37746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme                Log.i(TAG, "Stopped polling");
37869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
379923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
380923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
38169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
382923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
383d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Separate thread used only to take screenshots so it doesn't block the main thread.
384d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
385d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private final class ScreenshotHandler extends Handler {
386d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public ScreenshotHandler(String name) {
387d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            super(newLooper(name));
388d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
389d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
390d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        @Override
391d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        public void handleMessage(Message msg) {
392d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (msg.what != MSG_SCREENSHOT_REQUEST) {
393d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.e(TAG, "Invalid message type: " + msg.what);
394d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
395d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
396d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            handleScreenshotRequest(msg);
397d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
398d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
399d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
400fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private BugreportInfo getInfo(int id) {
401fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = mProcesses.get(id);
402d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
403fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "Not monitoring process with ID " + id);
404d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
405d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return info;
406d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
407d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
408d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
409923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Creates the {@link BugreportInfo} for a process and issue a system notification to
410923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * indicate its progress.
411923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     *
412923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * @return whether it succeeded or not.
413923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
414fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private boolean startProgress(String name, int id, int pid, int max) {
415923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (name == null) {
416923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
417923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
418fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (id == -1) {
419fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.e(TAG, "Missing " + EXTRA_ID + " on start intent");
420fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            return false;
421fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        }
422923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (pid == -1) {
423923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
424923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return false;
425923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
426923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (max <= 0) {
427923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
428923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return false;
42969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
43069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
431fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = new BugreportInfo(mContext, id, pid, name, max);
432fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (mProcesses.indexOfKey(id) >= 0) {
4331ae5a69bc495154d0baf504caa95d7eddbc7177cFelipe Leme            // BUGREPORT_STARTED intent was already received; ignore it.
434fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "ID " + id + " already watched");
4351ae5a69bc495154d0baf504caa95d7eddbc7177cFelipe Leme            return true;
436923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        }
4371ae5a69bc495154d0baf504caa95d7eddbc7177cFelipe Leme        mProcesses.put(info.id, info);
438923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        updateProgress(info);
439923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        return true;
440923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
44169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
442923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
44346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Updates the system notification for a given bugreport.
444923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
445923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void updateProgress(BugreportInfo info) {
446923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        if (info.max <= 0 || info.progress < 0) {
447923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            Log.e(TAG, "Invalid progress values for " + info);
448923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            return;
44969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
45069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
4519f3554176019543e654be7dba5410de2bbe3b55fWei Liu        if (info.finished) {
4529f3554176019543e654be7dba5410de2bbe3b55fWei Liu            Log.w(TAG, "Not sending progress notification because bugreport has finished already ("
4539f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    + info + ")");
4549f3554176019543e654be7dba5410de2bbe3b55fWei Liu            return;
4559f3554176019543e654be7dba5410de2bbe3b55fWei Liu        }
4569f3554176019543e654be7dba5410de2bbe3b55fWei Liu
457923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final NumberFormat nf = NumberFormat.getPercentInstance();
458923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        nf.setMinimumFractionDigits(2);
459923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        nf.setMaximumFractionDigits(2);
4603fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        final String percentageText = nf.format((double) info.progress / info.max);
4619f3554176019543e654be7dba5410de2bbe3b55fWei Liu
4629f3554176019543e654be7dba5410de2bbe3b55fWei Liu        String title = mContext.getString(R.string.bugreport_in_progress_title, info.id);
4639f3554176019543e654be7dba5410de2bbe3b55fWei Liu
4649f3554176019543e654be7dba5410de2bbe3b55fWei Liu        // TODO: Remove this workaround when notification progress is implemented on Wear.
4659f3554176019543e654be7dba5410de2bbe3b55fWei Liu        if (mIsWatch) {
4669f3554176019543e654be7dba5410de2bbe3b55fWei Liu            nf.setMinimumFractionDigits(0);
4679f3554176019543e654be7dba5410de2bbe3b55fWei Liu            nf.setMaximumFractionDigits(0);
4689f3554176019543e654be7dba5410de2bbe3b55fWei Liu            final String watchPercentageText = nf.format((double) info.progress / info.max);
4699f3554176019543e654be7dba5410de2bbe3b55fWei Liu            title = title + "\n" + watchPercentageText;
4709f3554176019543e654be7dba5410de2bbe3b55fWei Liu        }
471923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
472923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme        final String name =
473d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                info.name != null ? info.name : mContext.getString(R.string.bugreport_unnamed);
474923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
4759f3554176019543e654be7dba5410de2bbe3b55fWei Liu        final Notification.Builder builder = newBaseNotification(mContext)
476923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentTitle(title)
477923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setTicker(title)
478923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setContentText(name)
479923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme                .setProgress(info.max, info.progress, false)
4809f3554176019543e654be7dba5410de2bbe3b55fWei Liu                .setOngoing(true);
4819f3554176019543e654be7dba5410de2bbe3b55fWei Liu
4829f3554176019543e654be7dba5410de2bbe3b55fWei Liu        // Wear bugreport doesn't need the bug info dialog, screenshot and cancel action.
4839f3554176019543e654be7dba5410de2bbe3b55fWei Liu        if (!mIsWatch) {
4849f3554176019543e654be7dba5410de2bbe3b55fWei Liu            final Action cancelAction = new Action.Builder(null, mContext.getString(
4859f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    com.android.internal.R.string.cancel), newCancelIntent(mContext, info)).build();
4869f3554176019543e654be7dba5410de2bbe3b55fWei Liu            final Intent infoIntent = new Intent(mContext, BugreportProgressService.class);
4879f3554176019543e654be7dba5410de2bbe3b55fWei Liu            infoIntent.setAction(INTENT_BUGREPORT_INFO_LAUNCH);
4889f3554176019543e654be7dba5410de2bbe3b55fWei Liu            infoIntent.putExtra(EXTRA_ID, info.id);
4899f3554176019543e654be7dba5410de2bbe3b55fWei Liu            final PendingIntent infoPendingIntent =
4909f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    PendingIntent.getService(mContext, info.id, infoIntent,
4919f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    PendingIntent.FLAG_UPDATE_CURRENT);
4929f3554176019543e654be7dba5410de2bbe3b55fWei Liu            final Action infoAction = new Action.Builder(null,
4939f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    mContext.getString(R.string.bugreport_info_action),
4949f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    infoPendingIntent).build();
4959f3554176019543e654be7dba5410de2bbe3b55fWei Liu            final Intent screenshotIntent = new Intent(mContext, BugreportProgressService.class);
4969f3554176019543e654be7dba5410de2bbe3b55fWei Liu            screenshotIntent.setAction(INTENT_BUGREPORT_SCREENSHOT);
4979f3554176019543e654be7dba5410de2bbe3b55fWei Liu            screenshotIntent.putExtra(EXTRA_ID, info.id);
4989f3554176019543e654be7dba5410de2bbe3b55fWei Liu            PendingIntent screenshotPendingIntent = mTakingScreenshot ? null : PendingIntent
4999f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    .getService(mContext, info.id, screenshotIntent,
5009f3554176019543e654be7dba5410de2bbe3b55fWei Liu                            PendingIntent.FLAG_UPDATE_CURRENT);
5019f3554176019543e654be7dba5410de2bbe3b55fWei Liu            final Action screenshotAction = new Action.Builder(null,
5029f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    mContext.getString(R.string.bugreport_screenshot_action),
5039f3554176019543e654be7dba5410de2bbe3b55fWei Liu                    screenshotPendingIntent).build();
5049f3554176019543e654be7dba5410de2bbe3b55fWei Liu            builder.setContentIntent(infoPendingIntent)
5059f3554176019543e654be7dba5410de2bbe3b55fWei Liu                .setActions(infoAction, screenshotAction, cancelAction);
5062288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme        }
5079f3554176019543e654be7dba5410de2bbe3b55fWei Liu
508262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme        if (DEBUG) {
50969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            Log.d(TAG, "Sending 'Progress' notification for id " + info.id + " (pid " + info.pid
5103fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                    + "): " + percentageText);
511262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme        }
5129f3554176019543e654be7dba5410de2bbe3b55fWei Liu        sendForegroundabledNotification(info.id, builder.build());
51369c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    }
51469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme
51569c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    private void sendForegroundabledNotification(int id, Notification notification) {
51669c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        if (mForegroundId >= 0) {
51769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            if (DEBUG) Log.d(TAG, "Already running as foreground service");
51869c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            NotificationManager.from(mContext).notify(id, notification);
51969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        } else {
52069c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            mForegroundId = id;
52169c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            Log.d(TAG, "Start running as foreground service on id " + mForegroundId);
52269c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            startForeground(mForegroundId, notification);
52369c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        }
524923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
525923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
526923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
52746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Creates a {@link PendingIntent} for a notification action used to cancel a bugreport.
52846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     */
52946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    private static PendingIntent newCancelIntent(Context context, BugreportInfo info) {
53046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent intent = new Intent(INTENT_BUGREPORT_CANCEL);
53146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        intent.setClass(context, BugreportProgressService.class);
532fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        intent.putExtra(EXTRA_ID, info.id);
533fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        return PendingIntent.getService(context, info.id, intent,
534bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                PendingIntent.FLAG_UPDATE_CURRENT);
53546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    }
53646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
53746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    /**
53846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Finalizes the progress on a given bugreport and cancel its notification.
539923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
540fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void stopProgress(int id) {
541fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        if (mProcesses.indexOfKey(id) < 0) {
542fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.w(TAG, "ID not watched: " + id);
543d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
544fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "Removing ID " + id);
545fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mProcesses.remove(id);
54669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
54769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        // Must stop foreground service first, otherwise notif.cancel() will fail below.
54869c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        stopForegroundWhenDone(id);
54969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        Log.d(TAG, "stopProgress(" + id + "): cancel notification");
55069c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        NotificationManager.from(mContext).cancel(id);
5510f2daaf2f7f0f9a35512e452231fd34e743ddc51Felipe Leme        stopSelfWhenDone();
552923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
553923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme
554923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
555923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Cancels a bugreport upon user's request.
556923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
557fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void cancel(int id) {
5586605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_CANCEL);
559fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "cancel: ID=" + id);
560fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
561d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info != null && !info.finished) {
562fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
563d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
564d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            deleteScreenshots(info);
565bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
566fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        stopProgress(id);
567923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
5689cadb75714bea7d435f34c8b7d06f698fa907a8dFelipe Leme
569923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
570923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Poll {@link SystemProperties} to get the progress on each monitored process.
571923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     *
572923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * @return whether it should keep polling.
573923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
574923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private boolean pollProgress() {
575d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final int total = mProcesses.size();
576d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (total == 0) {
577d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.d(TAG, "No process to poll progress.");
578d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
579d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        int activeProcesses = 0;
580d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (int i = 0; i < total; i++) {
581d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final BugreportInfo info = mProcesses.valueAt(i);
582af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            if (info == null) {
583fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                Log.wtf(TAG, "pollProgress(): null info at index " + i + "(ID = "
584af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                        + mProcesses.keyAt(i) + ")");
585af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme                continue;
586af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            }
587af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme
588af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            final int pid = info.pid;
589fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            final int id = info.id;
590d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (info.finished) {
59185ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme                if (DEBUG) Log.v(TAG, "Skipping finished process " + pid + " (id: " + id + ")");
592d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                continue;
593923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme            }
594d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            activeProcesses++;
595d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final String progressKey = DUMPSTATE_PREFIX + pid + PROGRESS_SUFFIX;
5963fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            info.realProgress = SystemProperties.getInt(progressKey, 0);
5973fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            if (info.realProgress == 0) {
598d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                Log.v(TAG, "System property " + progressKey + " is not set yet");
599d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
6003fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            final String maxKey = DUMPSTATE_PREFIX + pid + MAX_SUFFIX;
6013fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            info.realMax = SystemProperties.getInt(maxKey, info.max);
6023fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            if (info.realMax <= 0 ) {
6033fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                Log.w(TAG, "Property " + maxKey + " is not positive: " + info.max);
6043fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                continue;
6053fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            }
6063fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            /*
6073fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme             * Checks whether the progress changed in a way that should be displayed to the user:
6083fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme             * - info.progress / info.max represents the displayed progress
6093fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme             * - info.realProgress / info.realMax represents the real progress
6103fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme             * - since the real progress can decrease, the displayed progress is only updated if it
6113fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme             *   increases
6123fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme             * - the displayed progress is capped at a maximum (like 99%)
6133fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme             */
6143fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            final int oldPercentage = (CAPPED_MAX * info.progress) / info.max;
6153fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            int newPercentage = (CAPPED_MAX * info.realProgress) / info.realMax;
6163fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            int max = info.realMax;
6173fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            int progress = info.realProgress;
6183fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme
6193fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            if (newPercentage > CAPPED_PROGRESS) {
6203fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                progress = newPercentage = CAPPED_PROGRESS;
6213fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                max = CAPPED_MAX;
6223fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            }
6233fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme
6243fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            if (newPercentage > oldPercentage) {
6253fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                if (DEBUG) {
6263fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                    if (progress != info.progress) {
6273fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                        Log.v(TAG, "Updating progress for PID " + pid + "(id: " + id + ") from "
6283fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                                + info.progress + " to " + progress);
6293fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                    }
6303fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                    if (max != info.max) {
6313fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                        Log.v(TAG, "Updating max progress for PID " + pid + "(id: " + id + ") from "
6323fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                                + info.max + " to " + max);
6333fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                    }
63469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                }
6353fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                info.progress = progress;
6363fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                info.max = max;
637d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                info.lastUpdate = System.currentTimeMillis();
638d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                updateProgress(info);
639d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            } else {
640d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                long inactiveTime = System.currentTimeMillis() - info.lastUpdate;
641d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (inactiveTime >= INACTIVITY_TIMEOUT) {
642fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    Log.w(TAG, "No progress update for PID " + pid + " since "
643d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                            + info.getFormattedLastUpdate());
644fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                    stopProgress(info.id);
64569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                }
64669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            }
64769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
648d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (DEBUG) Log.v(TAG, "pollProgress() total=" + total + ", actives=" + activeProcesses);
649d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return activeProcesses > 0;
650923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
65169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
652923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    /**
653bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Fetches a {@link BugreportInfo} for a given process and launches a dialog where the user can
654bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * change its values.
655bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
656fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void launchBugreportInfoDialog(int id) {
6576605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_DETAILS);
658bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // Copy values so it doesn't lock mProcesses while UI is being updated
659bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String name, title, description;
660fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
661d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
6621eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // Most likely am killed Shell before user tapped the notification. Since system might
6631eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // be too busy anwyays, it's better to ignore the notification and switch back to the
6641eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // non-interactive mode (where the bugerport will be shared upon completion).
665bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme            Log.w(TAG, "launchBugreportInfoDialog(): canceling notification because id " + id
666bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                    + " was not found");
6671eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // TODO: add test case to make sure notification is canceled.
66869c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            NotificationManager.from(mContext).cancel(id);
669d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
670d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
671d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
672d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        collapseNotificationBar();
673fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        mInfoDialog.initialize(mContext, info);
674d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
675d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
676d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
677d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Starting point for taking a screenshot.
678d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * <p>
679079f89614c49364bb907783b008827fbc306dd73Felipe Leme     * It first display a toast message and waits {@link #SCREENSHOT_DELAY_SECONDS} seconds before
680079f89614c49364bb907783b008827fbc306dd73Felipe Leme     * taking the screenshot.
681d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
682079f89614c49364bb907783b008827fbc306dd73Felipe Leme    private void takeScreenshot(int id) {
683079f89614c49364bb907783b008827fbc306dd73Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SCREENSHOT);
6841eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        if (getInfo(id) == null) {
6851eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // Most likely am killed Shell before user tapped the notification. Since system might
6861eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // be too busy anwyays, it's better to ignore the notification and switch back to the
6871eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // non-interactive mode (where the bugerport will be shared upon completion).
688bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme            Log.w(TAG, "takeScreenshot(): canceling notification because id " + id
689bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                    + " was not found");
6901eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            // TODO: add test case to make sure notification is canceled.
69169c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            NotificationManager.from(mContext).cancel(id);
6921eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme            return;
6931eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        }
694d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        setTakingScreenshot(true);
695079f89614c49364bb907783b008827fbc306dd73Felipe Leme        collapseNotificationBar();
696079f89614c49364bb907783b008827fbc306dd73Felipe Leme        final String msg = mContext.getResources()
697079f89614c49364bb907783b008827fbc306dd73Felipe Leme                .getQuantityString(com.android.internal.R.plurals.bugreport_countdown,
698079f89614c49364bb907783b008827fbc306dd73Felipe Leme                        SCREENSHOT_DELAY_SECONDS, SCREENSHOT_DELAY_SECONDS);
699079f89614c49364bb907783b008827fbc306dd73Felipe Leme        Log.i(TAG, msg);
700079f89614c49364bb907783b008827fbc306dd73Felipe Leme        // Show a toast just once, otherwise it might be captured in the screenshot.
701079f89614c49364bb907783b008827fbc306dd73Felipe Leme        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
702079f89614c49364bb907783b008827fbc306dd73Felipe Leme
703079f89614c49364bb907783b008827fbc306dd73Felipe Leme        takeScreenshot(id, SCREENSHOT_DELAY_SECONDS);
704d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
705d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
706d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
707d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Takes a screenshot after {@code delay} seconds.
708d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
709fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void takeScreenshot(int id, int delay) {
710d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (delay > 0) {
711fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "Taking screenshot for " + id + " in " + delay + " seconds");
712d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Message msg = mMainHandler.obtainMessage();
713d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.what = MSG_DELAYED_SCREENSHOT;
714fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            msg.arg1 = id;
715d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            msg.arg2 = delay - 1;
716d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mMainHandler.sendMessageDelayed(msg, DateUtils.SECOND_IN_MILLIS);
717d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
718d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
719d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
720d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        // It's time to take the screenshot: let the proper thread handle it
721fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
722d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
723d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
724d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
725d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final String screenshotPath =
726d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                new File(mScreenshotsDir, info.getPathNextScreenshot()).getAbsolutePath();
727d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
7288648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme        Message.obtain(mScreenshotHandler, MSG_SCREENSHOT_REQUEST, id, UNUSED_ARG2, screenshotPath)
7298648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme                .sendToTarget();
730d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
731d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
732d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
733d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Sets the internal {@code mTakingScreenshot} state and updates all notifications so their
734d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * SCREENSHOT button is enabled or disabled accordingly.
735d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
736d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void setTakingScreenshot(boolean flag) {
737d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        synchronized (BugreportProgressService.this) {
738d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            mTakingScreenshot = flag;
739d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            for (int i = 0; i < mProcesses.size(); i++) {
7402288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                final BugreportInfo info = mProcesses.valueAt(i);
7412288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                if (info.finished) {
742abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme                    Log.d(TAG, "Not updating progress for " + info.id + " while taking screenshot"
743abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme                            + " because share notification was already sent");
7442288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                    continue;
7452288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                }
7462288129d5208cd26ab41191db69a418d15ead9eeFelipe Leme                updateProgress(info);
747bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
748bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
749d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
750bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
751d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void handleScreenshotRequest(Message requestMsg) {
752d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        String screenshotFile = (String) requestMsg.obj;
753d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        boolean taken = takeScreenshot(mContext, screenshotFile);
754d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        setTakingScreenshot(false);
755d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
7568648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme        Message.obtain(mMainHandler, MSG_SCREENSHOT_RESPONSE, requestMsg.arg1, taken ? 1 : 0,
7578648a15406d43c8af12e9cfe2355b1eee201d479Felipe Leme                screenshotFile).sendToTarget();
758d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
759bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
760d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void handleScreenshotResponse(Message resultMsg) {
761d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final boolean taken = resultMsg.arg2 != 0;
762d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final BugreportInfo info = getInfo(resultMsg.arg1);
763d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
764d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
765d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
766d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final File screenshotFile = new File((String) resultMsg.obj);
767d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
7685d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme        final String msg;
769d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (taken) {
770d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.addScreenshot(screenshotFile);
771c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            if (info.finished) {
772c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                Log.d(TAG, "Screenshot finished after bugreport; updating share notification");
773c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                info.renameScreenshots(mScreenshotsDir);
77469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                sendBugreportNotification(info, mTakingScreenshot);
775c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
7765d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            msg = mContext.getString(R.string.bugreport_screenshot_taken);
777d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        } else {
7785d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            msg = mContext.getString(R.string.bugreport_screenshot_failed);
7795d9000aa45c19de0e7ec4131c1aca3d366e9a793Felipe Leme            Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
780d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
781d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        Log.d(TAG, msg);
782d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
783d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
784d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
785d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Deletes all screenshots taken for a given bugreport.
786d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
787d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void deleteScreenshots(BugreportInfo info) {
788d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (File file : info.screenshotFiles) {
789d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Log.i(TAG, "Deleting screenshot file " + file);
790d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            file.delete();
791d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
792bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
793bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
794bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
79569c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme     * Stop running on foreground once there is no more active bugreports being watched.
79669c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme     */
79769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    private void stopForegroundWhenDone(int id) {
79869c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        if (id != mForegroundId) {
79969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            Log.d(TAG, "stopForegroundWhenDone(" + id + "): ignoring since foreground id is "
80069c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                    + mForegroundId);
80169c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            return;
80269c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        }
80369c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme
80469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        Log.d(TAG, "detaching foreground from id " + mForegroundId);
80569c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        stopForeground(Service.STOP_FOREGROUND_DETACH);
80669c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        mForegroundId = -1;
80769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme
80869c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        // Might need to restart foreground using a new notification id.
80969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        final int total = mProcesses.size();
81069c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        if (total > 0) {
81169c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            for (int i = 0; i < total; i++) {
81269c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                final BugreportInfo info = mProcesses.valueAt(i);
81369c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                if (!info.finished) {
81469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                    updateProgress(info);
81569c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                    break;
81669c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                }
81769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            }
81869c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        }
81969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    }
82069c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme
82169c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    /**
822923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     * Finishes the service when it's not monitoring any more processes.
823923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme     */
824923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    private void stopSelfWhenDone() {
825d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (mProcesses.size() > 0) {
826fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (DEBUG) Log.d(TAG, "Staying alive, waiting for IDs " + mProcesses);
827d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
82869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
829fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "No more processes to handle, shutting down");
830d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        stopSelf();
831923afa9fe1bbc9a5395309622e596ad03c3d8481Felipe Leme    }
83269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
833bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
834bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
835bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
836fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void onBugreportFinished(int id, Intent intent) {
837af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
838c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin        // Since BugreportProvider and BugreportProgressService aren't tightly coupled,
839c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin        // we need to make sure they are explicitly tied to a single unique notification URI
840c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin        // so that the service can alert the provider of changes it has done (ie. new bug
841c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin        // reports)
842c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin        // See { @link Cursor#setNotificationUri } and {@link ContentResolver#notifyChanges }
843c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin        final Uri notificationUri = BugreportStorageProvider.getNotificationUri();
844c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin        mContext.getContentResolver().notifyChange(notificationUri, null, false);
845c6905cfb1133627dfd500491c60b6528a3e593e0Ben Lin
846af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        if (bugreportFile == null) {
847af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            // Should never happen, dumpstate always set the file.
848af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent);
849af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            return;
850af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        }
851fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        mInfoDialog.onBugreportFinished(id);
852fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo info = getInfo(id);
853d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
854d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED first.
855fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.v(TAG, "Creating info for untracked ID " + id);
856fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            info = new BugreportInfo(mContext, id);
857fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mProcesses.put(id, info);
858d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
859d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.renameScreenshots(mScreenshotsDir);
860af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme        info.bugreportFile = bugreportFile;
861af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme
862510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        final int max = intent.getIntExtra(EXTRA_MAX, -1);
863510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        if (max != -1) {
864510e922e47fec69839dd48c5473540f93d79a508Felipe Leme            MetricsLogger.histogram(this, "dumpstate_duration", max);
865510e922e47fec69839dd48c5473540f93d79a508Felipe Leme            info.max = max;
866510e922e47fec69839dd48c5473540f93d79a508Felipe Leme        }
867510e922e47fec69839dd48c5473540f93d79a508Felipe Leme
868d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final File screenshot = getFileExtra(intent, EXTRA_SCREENSHOT);
869d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (screenshot != null) {
870d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.addScreenshot(screenshot);
87146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
872d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.finished = true;
87369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
87469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        // Stop running on foreground, otherwise share notification cannot be dismissed.
87569c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        stopForegroundWhenDone(id);
87669c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme
8779f3554176019543e654be7dba5410de2bbe3b55fWei Liu        triggerLocalNotification(mContext, info);
878b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
879b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
880b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
88169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * Responsible for triggering a notification that allows the user to start a "share" intent with
88246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * the bugreport. On watches we have other methods to allow the user to start this intent
88369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * (usually by triggering it on another connected device); we don't need to display the
88469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     * notification in this case.
885b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
886d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void triggerLocalNotification(final Context context, final BugreportInfo info) {
88746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
88846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
889d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            Toast.makeText(context, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
890fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            stopProgress(info.id);
891b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return;
892b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
893b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
89446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
895b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (!isPlainText) {
896b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Already zipped, send it right away.
89769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            sendBugreportNotification(info, mTakingScreenshot);
898b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
899b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            // Asynchronously zip the file first, then send it.
90069c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            sendZippedBugreportNotification(info, mTakingScreenshot);
901b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
902b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
903b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
904b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Intent buildWarningIntent(Context context, Intent sendIntent) {
905b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Intent intent = new Intent(context, BugreportWarningActivity.class);
906b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
907b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return intent;
908b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
909b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
910b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
911b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Build {@link Intent} that can be used to share the given bugreport.
912b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
913bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static Intent buildSendIntent(Context context, BugreportInfo info) {
914bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // Files are kept on private storage, so turn into Uris that we can
915bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        // grant temporary permissions for.
916abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        final Uri bugreportUri;
917abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        try {
918abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            bugreportUri = getUri(context, info.bugreportFile);
919abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        } catch (IllegalArgumentException e) {
920abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            // Should not happen on production, but happens when a Shell is sideloaded and
921abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            // FileProvider cannot find a configured root for it.
922abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            Log.wtf(TAG, "Could not get URI for " + info.bugreportFile, e);
923abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            return null;
924abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        }
925bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
926b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
927b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final String mimeType = "application/vnd.android.bugreport";
928b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
929b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.addCategory(Intent.CATEGORY_DEFAULT);
930b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.setType(mimeType);
931b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
932c8e2b6092c0fbf87e71f81fd2cffbb29ff8d9039Felipe Leme        final String subject = !TextUtils.isEmpty(info.title) ?
933c8e2b6092c0fbf87e71f81fd2cffbb29ff8d9039Felipe Leme                info.title : bugreportUri.getLastPathSegment();
934bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
935b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
936b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
937b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
938b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // create the ClipData object with the attachments URIs.
939d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final StringBuilder messageBody = new StringBuilder("Build info: ")
940bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append(SystemProperties.get("ro.build.description"))
941bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append("\nSerial number: ")
942bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            .append(SystemProperties.get("ro.serialno"));
943bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        if (!TextUtils.isEmpty(info.description)) {
944bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            messageBody.append("\nDescription: ").append(info.description);
945bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
946bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        intent.putExtra(Intent.EXTRA_TEXT, messageBody.toString());
947b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final ClipData clipData = new ClipData(null, new String[] { mimeType },
948b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                new ClipData.Item(null, null, null, bugreportUri));
949b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
950d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        for (File screenshot : info.screenshotFiles) {
951d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final Uri screenshotUri = getUri(context, screenshot);
952b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
953b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            attachments.add(screenshotUri);
954b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
955b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.setClipData(clipData);
956b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
957b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
958b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final Account sendToAccount = findSendToAccount(context);
959b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (sendToAccount != null) {
960b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
961b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
962b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
963b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return intent;
964b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
965b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
966b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
96746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Shares the bugreport upon user's request by issuing a {@link Intent#ACTION_SEND_MULTIPLE}
96846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * intent, but issuing a warning dialog the first time.
969b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
970fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void shareBugreport(int id, BugreportInfo sharedInfo) {
9716605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SHARE);
972fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo info = getInfo(id);
973d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
974c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            // Service was terminated but notification persisted
975c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            info = sharedInfo;
976fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            Log.d(TAG, "shareBugreport(): no info for ID " + id + " on managed processes ("
977c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                    + mProcesses + "), using info from intent instead (" + info + ")");
9784f663f6c25488a83a438a01dfef8372aa4e6b2aaFelipe Leme        } else {
9794f663f6c25488a83a438a01dfef8372aa4e6b2aaFelipe Leme            Log.v(TAG, "shareBugReport(): id " + id + " info = " + info);
98046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
9814967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
98269c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        addDetailsToZipFile(info);
9834967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
984d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final Intent sendIntent = buildSendIntent(mContext, info);
985abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        if (sendIntent == null) {
986abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            Log.w(TAG, "Stopping progres on ID " + id + " because share intent could not be built");
987abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            stopProgress(id);
988abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            return;
989abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        }
990abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme
99146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent notifIntent;
992b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
993b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        // Send through warning dialog by default
994fcca68dfb137c061952d23e1873e995e6bcf172dFelipe Leme        if (getWarningState(mContext, STATE_UNKNOWN) != STATE_HIDE) {
995d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            notifIntent = buildWarningIntent(mContext, sendIntent);
996b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
997b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            notifIntent = sendIntent;
998b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
999b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1000b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
100146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        // Send the share intent...
1002d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        mContext.startActivity(notifIntent);
100346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
100446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        // ... and stop watching this process.
1005fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        stopProgress(id);
100646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    }
100746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
100846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme    /**
10092758d5d93970f26867d778c944605371e55b751eFelipe Leme     * Sends a notification indicating the bugreport has finished so use can share it.
101046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     */
101169c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    private void sendBugreportNotification(BugreportInfo info, boolean takingScreenshot) {
101218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme
101318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        // Since adding the details can take a while, do it before notifying user.
101469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        addDetailsToZipFile(info);
101518b5892950b7f21e66c9268129323cbc0e865699Felipe Leme
101646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE);
101769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        shareIntent.setClass(mContext, BugreportProgressService.class);
101846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        shareIntent.setAction(INTENT_BUGREPORT_SHARE);
1019fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        shareIntent.putExtra(EXTRA_ID, info.id);
1020c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        shareIntent.putExtra(EXTRA_INFO, info);
102146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
102269c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        final String title = mContext.getString(R.string.bugreport_finished_title, info.id);
1023a43d139359346ad57604e8335d92de57f3d47171Felipe Leme        final String content = takingScreenshot ?
102469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                mContext.getString(R.string.bugreport_finished_pending_screenshot_text)
102569c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                : mContext.getString(R.string.bugreport_finished_text);
102669c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        final Notification.Builder builder = newBaseNotification(mContext)
102769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                .setContentTitle(title)
102869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                .setTicker(title)
10295ee846dd18022307341c9808c9aacded2b2f60fdFelipe Leme                .setContentText(content)
103069c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                .setContentIntent(PendingIntent.getService(mContext, info.id, shareIntent,
1031bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        PendingIntent.FLAG_UPDATE_CURRENT))
103269c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                .setDeleteIntent(newCancelIntent(mContext, info));
1033b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1034bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        if (!TextUtils.isEmpty(info.name)) {
1035bc3b0458bf4281a92f65dc98d87ad6ff40c92f98Selim Cinek            builder.setSubText(info.name);
1036bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1037bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1038fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        Log.v(TAG, "Sending 'Share' notification for ID " + info.id + ": " + title);
103969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        NotificationManager.from(mContext).notify(info.id, builder.build());
1040b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1041b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1042b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
10432758d5d93970f26867d778c944605371e55b751eFelipe Leme     * Sends a notification indicating the bugreport is being updated so the user can wait until it
10442758d5d93970f26867d778c944605371e55b751eFelipe Leme     * finishes - at this point there is nothing to be done other than waiting, hence it has no
10452758d5d93970f26867d778c944605371e55b751eFelipe Leme     * pending action.
10462758d5d93970f26867d778c944605371e55b751eFelipe Leme     */
104769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    private void sendBugreportBeingUpdatedNotification(Context context, int id) {
10482758d5d93970f26867d778c944605371e55b751eFelipe Leme        final String title = context.getString(R.string.bugreport_updating_title);
1049208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        final Notification.Builder builder = newBaseNotification(context)
10502758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setContentTitle(title)
10512758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setTicker(title)
1052208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme                .setContentText(context.getString(R.string.bugreport_updating_wait));
1053208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        Log.v(TAG, "Sending 'Updating zip' notification for ID " + id + ": " + title);
105469c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        sendForegroundabledNotification(id, builder.build());
1055208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme    }
1056208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme
1057208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme    private static Notification.Builder newBaseNotification(Context context) {
105865a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Leme        if (sNotificationBundle.isEmpty()) {
105965a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Leme            // Rename notifcations from "Shell" to "Android System"
106065a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Leme            sNotificationBundle.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
106165a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Leme                    context.getString(com.android.internal.R.string.android_system_label));
106265a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Leme        }
1063208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme        return new Notification.Builder(context)
106465a9c6760ef6cf1c8e1762a271aa43c626d27048Felipe Leme                .addExtras(sNotificationBundle)
1065208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme                .setCategory(Notification.CATEGORY_SYSTEM)
1066208b1881ae924cd0c2bed326555e4aa18424d927Felipe Leme                .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
10672758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setLocalOnly(true)
10682758d5d93970f26867d778c944605371e55b751eFelipe Leme                .setColor(context.getColor(
10692758d5d93970f26867d778c944605371e55b751eFelipe Leme                        com.android.internal.R.color.system_notification_accent_color));
10702758d5d93970f26867d778c944605371e55b751eFelipe Leme    }
10712758d5d93970f26867d778c944605371e55b751eFelipe Leme
10722758d5d93970f26867d778c944605371e55b751eFelipe Leme    /**
1073b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Sends a zipped bugreport notification.
1074b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
107569c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    private void sendZippedBugreportNotification( final BugreportInfo info,
107669c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            final boolean takingScreenshot) {
1077b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        new AsyncTask<Void, Void, Void>() {
1078b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            @Override
1079b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            protected Void doInBackground(Void... params) {
10804967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                zipBugreport(info);
108169c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme                sendBugreportNotification(info, takingScreenshot);
1082b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                return null;
1083b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
1084b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }.execute();
1085b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1086b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1087b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
1088b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Zips a bugreport file, returning the path to the new file (or to the
1089b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * original in case of failure).
1090b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
10914967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void zipBugreport(BugreportInfo info) {
10924967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final String bugreportPath = info.bugreportFile.getAbsolutePath();
10934967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final String zippedPath = bugreportPath.replace(".txt", ".zip");
1094b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
10954967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File bugreportZippedFile = new File(zippedPath);
10964967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        try (InputStream is = new FileInputStream(info.bugreportFile);
109769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                ZipOutputStream zos = new ZipOutputStream(
109869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme                        new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
10994967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, info.bugreportFile.getName(), is);
11004967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // Delete old file
11014967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            final boolean deleted = info.bugreportFile.delete();
1102b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            if (deleted) {
1103b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
1104b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            } else {
1105b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
1106b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
11074967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            info.bugreportFile = bugreportZippedFile;
1108b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } catch (IOException e) {
110969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            Log.e(TAG, "exception zipping file " + zippedPath, e);
1110b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1111b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1112b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1113b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    /**
11144967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * Adds the user-provided info into the bugreport zip file.
11154967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * <p>
11164967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * If user provided a title, it will be saved into a {@code title.txt} entry; similarly, the
11174967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     * description will be saved on {@code description.txt}.
11184967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme     */
111969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme    private void addDetailsToZipFile(BugreportInfo info) {
1120c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        if (info.bugreportFile == null) {
1121c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            // One possible reason is a bug in the Parcelization code.
1122af6fd4086c8f24e8b70a810fe83081b67e5db236Felipe Leme            Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
1123c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return;
1124c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1125b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme        if (TextUtils.isEmpty(info.title) && TextUtils.isEmpty(info.description)) {
1126b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme            Log.d(TAG, "Not touching zip file since neither title nor description are set");
1127b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme            return;
1128b9d598c47b69406174b015ec9f064448e4b3b8b5Felipe Leme        }
112918b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        if (info.addedDetailsToZip || info.addingDetailsToZip) {
113018b5892950b7f21e66c9268129323cbc0e865699Felipe Leme            Log.d(TAG, "Already added details to zip file for " + info);
113118b5892950b7f21e66c9268129323cbc0e865699Felipe Leme            return;
113218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        }
113318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        info.addingDetailsToZip = true;
11342758d5d93970f26867d778c944605371e55b751eFelipe Leme
11354967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        // It's not possible to add a new entry into an existing file, so we need to create a new
11364967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        // zip, copy all entries, then rename it.
113769c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme        sendBugreportBeingUpdatedNotification(mContext, info.id); // ...and that takes time
113869c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme
11394967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File dir = info.bugreportFile.getParentFile();
11404967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
11412758d5d93970f26867d778c944605371e55b751eFelipe Leme        Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
11424967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        try (ZipFile oldZip = new ZipFile(info.bugreportFile);
11434967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
11444967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
11454967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // First copy contents from original zip.
11464967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Enumeration<? extends ZipEntry> entries = oldZip.entries();
11474967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            while (entries.hasMoreElements()) {
11484967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                final ZipEntry entry = entries.nextElement();
11494967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                final String entryName = entry.getName();
11504967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                if (!entry.isDirectory()) {
11514967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                    addEntry(zos, entryName, entry.getTime(), oldZip.getInputStream(entry));
11524967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                } else {
11534967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                    Log.w(TAG, "skipping directory entry: " + entryName);
11544967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme                }
11554967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            }
11564967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
11574967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            // Then add the user-provided info.
11584967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, "title.txt", info.title);
11594967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, "description.txt", info.description);
11604967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        } catch (IOException e) {
11614967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Log.e(TAG, "exception zipping file " + tmpZip, e);
116245a905bbafcbcbf89933e9b9226977995bae1afaFelipe Leme            Toast.makeText(mContext, R.string.bugreport_add_details_to_zip_failed,
116345a905bbafcbcbf89933e9b9226977995bae1afaFelipe Leme                    Toast.LENGTH_LONG).show();
11644967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            return;
116551a4ede593b1bba96d7cc14fca54e8e5f02850e0Felipe Leme        } finally {
116651a4ede593b1bba96d7cc14fca54e8e5f02850e0Felipe Leme            // Make sure it only tries to add details once, even it fails the first time.
116751a4ede593b1bba96d7cc14fca54e8e5f02850e0Felipe Leme            info.addedDetailsToZip = true;
116851a4ede593b1bba96d7cc14fca54e8e5f02850e0Felipe Leme            info.addingDetailsToZip = false;
116969c53e65b941235a8d2e193d5b9a783f7787af0dFelipe Leme            stopForegroundWhenDone(info.id);
11704967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
11714967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
11724967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (!tmpZip.renameTo(info.bugreportFile)) {
11734967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
11744967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
11754967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
11764967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
11774967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entry, String text)
11784967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            throws IOException {
11794967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (DEBUG) Log.v(TAG, "adding entry '" + entry + "': " + text);
11804967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (!TextUtils.isEmpty(text)) {
11814967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            addEntry(zos, entry, new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)));
11824967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        }
11834967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
11844967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
11854967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entryName, InputStream is)
11864967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            throws IOException {
11874967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        addEntry(zos, entryName, System.currentTimeMillis(), is);
11884967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
11894967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
11904967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    private static void addEntry(ZipOutputStream zos, String entryName, long timestamp,
11914967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme            InputStream is) throws IOException {
11924967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final ZipEntry entry = new ZipEntry(entryName);
11934967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        entry.setTime(timestamp);
11944967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        zos.putNextEntry(entry);
11954967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        final int totalBytes = Streams.copy(is, zos);
11964967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        if (DEBUG) Log.v(TAG, "size of '" + entryName + "' entry: " + totalBytes + " bytes");
11974967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme        zos.closeEntry();
11984967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    }
11994967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme
12004967f737d9906e3d5c9bf3a0584a7b7cf83b5a8cFelipe Leme    /**
1201b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     * Find the best matching {@link Account} based on build properties.
1202b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme     */
1203b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    private static Account findSendToAccount(Context context) {
1204b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final AccountManager am = (AccountManager) context.getSystemService(
1205b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                Context.ACCOUNT_SERVICE);
1206b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1207b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
1208b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (!preferredDomain.startsWith("@")) {
1209b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            preferredDomain = "@" + preferredDomain;
1210b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1211b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1212213e355c77993c718298c89bc04908a0d6c5adddFelipe Leme        final Account[] accounts;
1213213e355c77993c718298c89bc04908a0d6c5adddFelipe Leme        try {
1214213e355c77993c718298c89bc04908a0d6c5adddFelipe Leme            accounts = am.getAccounts();
1215213e355c77993c718298c89bc04908a0d6c5adddFelipe Leme        } catch (RuntimeException e) {
1216213e355c77993c718298c89bc04908a0d6c5adddFelipe Leme            Log.e(TAG, "Could not get accounts for preferred domain " + preferredDomain, e);
1217213e355c77993c718298c89bc04908a0d6c5adddFelipe Leme            return null;
1218213e355c77993c718298c89bc04908a0d6c5adddFelipe Leme        }
1219213e355c77993c718298c89bc04908a0d6c5adddFelipe Leme        if (DEBUG) Log.d(TAG, "Number of accounts: " + accounts.length);
1220b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        Account foundAccount = null;
1221b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        for (Account account : accounts) {
1222b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
1223b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                if (!preferredDomain.isEmpty()) {
1224b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // if we have a preferred domain and it matches, return; otherwise keep
1225b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // looking
1226b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    if (account.name.endsWith(preferredDomain)) {
1227b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        return account;
1228b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    } else {
1229b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                        foundAccount = account;
1230b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    }
1231b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // if we don't have a preferred domain, just return since it looks like
1232b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    // an email address
1233b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                } else {
1234b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                    return account;
1235b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme                }
1236b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            }
1237b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1238b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return foundAccount;
1239b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1240b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1241226940ed8550c02875a987f7e46699e6003ec1c0Michal Karpinski    static Uri getUri(Context context, File file) {
1242b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
1243b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
1244b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme
1245b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    static File getFileExtra(Intent intent, String key) {
1246b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        final String path = intent.getStringExtra(key);
1247b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        if (path != null) {
1248b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return new File(path);
1249b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        } else {
1250b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme            return null;
1251b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme        }
1252b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme    }
125369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
1254abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme    /**
1255abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme     * Dumps an intent, extracting the relevant extras.
1256abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme     */
1257abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme    static String dumpIntent(Intent intent) {
1258abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        if (intent == null) {
1259abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            return "NO INTENT";
1260abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        }
1261abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        String action = intent.getAction();
1262abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        if (action == null) {
1263abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            // Happens when BugreportReceiver calls startService...
1264abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            action = "no action";
1265abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        }
1266abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        final StringBuilder buffer = new StringBuilder(action).append(" extras: ");
1267abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        addExtra(buffer, intent, EXTRA_ID);
1268abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        addExtra(buffer, intent, EXTRA_PID);
1269abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        addExtra(buffer, intent, EXTRA_MAX);
1270abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        addExtra(buffer, intent, EXTRA_NAME);
1271abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        addExtra(buffer, intent, EXTRA_DESCRIPTION);
1272abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        addExtra(buffer, intent, EXTRA_BUGREPORT);
1273abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        addExtra(buffer, intent, EXTRA_SCREENSHOT);
1274abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        addExtra(buffer, intent, EXTRA_INFO);
1275abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme
1276abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        if (intent.hasExtra(EXTRA_ORIGINAL_INTENT)) {
1277abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            buffer.append(SHORT_EXTRA_ORIGINAL_INTENT).append(": ");
1278abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            final Intent originalIntent = intent.getParcelableExtra(EXTRA_ORIGINAL_INTENT);
1279abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            buffer.append(dumpIntent(originalIntent));
1280abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        } else {
1281abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            buffer.append("no ").append(SHORT_EXTRA_ORIGINAL_INTENT);
1282abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        }
1283abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme
1284abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        return buffer.toString();
1285abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme    }
1286abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme
1287abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme    private static final String SHORT_EXTRA_ORIGINAL_INTENT =
1288abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            EXTRA_ORIGINAL_INTENT.substring(EXTRA_ORIGINAL_INTENT.lastIndexOf('.') + 1);
1289abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme
1290abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme    private static void addExtra(StringBuilder buffer, Intent intent, String name) {
1291abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        final String shortName = name.substring(name.lastIndexOf('.') + 1);
1292abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        if (intent.hasExtra(name)) {
1293abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            buffer.append(shortName).append('=').append(intent.getExtra(name));
1294abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        } else {
1295abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme            buffer.append("no ").append(shortName);
1296abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        }
1297abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme        buffer.append(", ");
1298abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme    }
1299abeab723d10b56302b86b1929ea994cf4a861fd0Felipe Leme
1300bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private static boolean setSystemProperty(String key, String value) {
1301bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        try {
130285ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            if (DEBUG) Log.v(TAG, "Setting system property " + key + " to " + value);
1303bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            SystemProperties.set(key, value);
1304bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        } catch (IllegalArgumentException e) {
1305bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            Log.e(TAG, "Could not set property " + key + " to " + value, e);
1306bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            return false;
1307bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1308bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return true;
1309bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1310bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1311bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1312bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
1313bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1314bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private boolean setBugreportNameProperty(int pid, String name) {
1315bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        Log.d(TAG, "Updating bugreport name to " + name);
1316bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
1317bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return setSystemProperty(key, name);
1318bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1319bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1320bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1321bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Updates the user-provided details of a bugreport.
1322bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1323fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme    private void updateBugreportInfo(int id, String name, String title, String description) {
1324fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final BugreportInfo info = getInfo(id);
1325d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        if (info == null) {
1326d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return;
1327d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
13286605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        if (title != null && !title.equals(info.title)) {
13296605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_TITLE_CHANGED);
13306605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        }
1331d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.title = title;
13326605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        if (description != null && !description.equals(info.description)) {
13336605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_DESCRIPTION_CHANGED);
13346605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        }
1335d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        info.description = description;
13361eee1996f5d57b42d4beed2790321480fbe03a4cFelipe Leme        if (name != null && !name.equals(info.name)) {
13376605bd89c53494b59717a826f9a17641bc32da41Felipe Leme            MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_DETAILS_NAME_CHANGED);
1338d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            info.name = name;
1339d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            updateProgress(info);
1340d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1341d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1342d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1343d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private void collapseNotificationBar() {
1344d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
1345d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1346d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1347d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    private static Looper newLooper(String name) {
1348d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        final HandlerThread thread = new HandlerThread(name, THREAD_PRIORITY_BACKGROUND);
1349d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        thread.start();
1350d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return thread.getLooper();
1351d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    }
1352d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1353d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme    /**
1354d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     * Takes a screenshot and save it to the given location.
1355d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme     */
1356aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme    private static boolean takeScreenshot(Context context, String path) {
1357aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme        final Bitmap bitmap = Screenshooter.takeScreenshot();
1358aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme        if (bitmap == null) {
1359aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme            return false;
1360aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme        }
1361aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme        boolean status;
1362aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme        try (final FileOutputStream fos = new FileOutputStream(path)) {
1363aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme            if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)) {
1364aa00f2d909dcc48b61b9338cd2ab7c33850a69d9Felipe Leme                ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)).vibrate(150);
1365d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return true;
1366aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme            } else {
1367aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme                Log.e(TAG, "Failed to save screenshot on " + path);
1368bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1369aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme        } catch (IOException e ) {
1370aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme            Log.e(TAG, "Failed to save screenshot on " + path, e);
1371aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme            return false;
1372aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme        } finally {
1373aba9743643d85e9bf5627da9d1fdc8ded25f22deFelipe Leme            bitmap.recycle();
1374bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1375d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        return false;
1376bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1377bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1378bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1379bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Checks whether a character is valid on bugreport names.
1380bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1381bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    @VisibleForTesting
1382bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    static boolean isValid(char c) {
1383bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
1384bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                || c == '_' || c == '-';
1385bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1386bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1387bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    /**
1388bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * Helper class encapsulating the UI elements and logic used to display a dialog where user
1389bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     * can change the details of a bugreport.
1390bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme     */
1391bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    private final class BugreportInfoDialog {
1392bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoName;
1393bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoTitle;
1394bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private EditText mInfoDescription;
1395bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private AlertDialog mDialog;
1396bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private Button mOkButton;
1397fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        private int mId;
1398bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private int mPid;
1399bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1400bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1401bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Last "committed" value of the bugreport name.
1402bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1403bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Once initially set, it's only updated when user clicks the OK button.
1404bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1405bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private String mSavedName;
1406bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1407bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1408bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Last value of the bugreport name as entered by the user.
1409bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1410bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Every time it's changed the equivalent system property is changed as well, but if the
1411bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
1412bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>
1413bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
1414bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * user changed the name but didn't clicked OK yet (for example, because the user is typing
1415bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * the description). The only drawback is that if the user changes the name while
1416bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
1417bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
1418bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
1419bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * such drawback.
1420bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1421bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        private String mTempName;
1422bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1423bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1424bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Sets its internal state and displays the dialog.
1425bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
14266605bd89c53494b59717a826f9a17641bc32da41Felipe Leme        private void initialize(final Context context, BugreportInfo info) {
1427262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme            final String dialogTitle =
1428262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                    context.getString(R.string.bugreport_info_dialog_title, info.id);
1429bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // First initializes singleton.
1430bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mDialog == null) {
1431bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                @SuppressLint("InflateParams")
1432bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                // It's ok pass null ViewRoot on AlertDialogs.
1433bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                final View view = View.inflate(context, R.layout.dialog_bugreport_info, null);
1434bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1435bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName = (EditText) view.findViewById(R.id.name);
1436bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoTitle = (EditText) view.findViewById(R.id.title);
1437bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoDescription = (EditText) view.findViewById(R.id.description);
1438bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1439bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
1440bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1441bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    @Override
1442bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    public void onFocusChange(View v, boolean hasFocus) {
1443bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        if (hasFocus) {
1444bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                            return;
1445bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        }
1446bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        sanitizeName();
1447bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    }
1448bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                });
1449bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1450bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mDialog = new AlertDialog.Builder(context)
1451bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setView(view)
1452262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                        .setTitle(dialogTitle)
1453bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setCancelable(false)
1454bbd91e58444f092e1080f5a0a746fcd4b21ce113Felipe Leme                        .setPositiveButton(context.getString(R.string.save),
1455bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                null)
1456bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .setNegativeButton(context.getString(com.android.internal.R.string.cancel),
1457bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                new DialogInterface.OnClickListener()
1458bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                {
1459bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    @Override
1460bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    public void onClick(DialogInterface dialog, int id)
1461bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    {
14626605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                                        MetricsLogger.action(context,
14636605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                                                MetricsEvent.ACTION_BUGREPORT_DETAILS_CANCELED);
1464bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                        if (!mTempName.equals(mSavedName)) {
1465bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            // Must restore dumpstate's name since it was changed
1466bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            // before user clicked OK.
1467bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                            setBugreportNameProperty(mPid, mSavedName);
1468bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                        }
1469bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                    }
1470bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                })
1471bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        .create();
1472bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1473bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mDialog.getWindow().setAttributes(
1474bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        new WindowManager.LayoutParams(
1475bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG));
1476bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1477262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme            } else {
1478262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                // Re-use view, but reset fields first.
1479262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mDialog.setTitle(dialogTitle);
1480262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoName.setText(null);
1481262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoTitle.setText(null);
1482262887834c7938b7a7bbbd7ef026b5965d6412fcFelipe Leme                mInfoDescription.setText(null);
1483bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1484bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1485bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // Then set fields.
1486fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mSavedName = mTempName = info.name;
1487fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mId = info.id;
1488fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            mPid = info.pid;
1489fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.name)) {
1490fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoName.setText(info.name);
1491bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1492fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.title)) {
1493fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoTitle.setText(info.title);
1494bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1495fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            if (!TextUtils.isEmpty(info.description)) {
1496fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                mInfoDescription.setText(info.description);
1497bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1498bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1499bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // And finally display it.
1500bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mDialog.show();
1501bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1502bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // TODO: in a traditional AlertDialog, when the positive button is clicked the
1503bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // dialog is always closed, but we need to validate the name first, so we need to
1504bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // get a reference to it, which is only available after it's displayed.
1505bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // It would be cleaner to use a regular dialog instead, but let's keep this
1506bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // workaround for now and change it later, when we add another button to take
1507bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // extra screenshots.
1508bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mOkButton == null) {
1509bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mOkButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
1510bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mOkButton.setOnClickListener(new View.OnClickListener() {
1511bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1512bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    @Override
1513bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    public void onClick(View view) {
15146605bd89c53494b59717a826f9a17641bc32da41Felipe Leme                        MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
1515bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        sanitizeName();
1516bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String name = mInfoName.getText().toString();
1517bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String title = mInfoTitle.getText().toString();
1518bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        final String description = mInfoDescription.getText().toString();
1519bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1520fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme                        updateBugreportInfo(mId, name, title, description);
1521bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                        mDialog.dismiss();
1522bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    }
1523bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                });
1524bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1525bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1526bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1527bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1528bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Sanitizes the user-provided value for the {@code name} field, automatically replacing
1529bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * invalid characters if necessary.
1530bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1531d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        private void sanitizeName() {
1532bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            String name = mInfoName.getText().toString();
1533bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (name.equals(mTempName)) {
1534bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
1535bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                return;
1536bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1537bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            final StringBuilder safeName = new StringBuilder(name.length());
1538bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            boolean changed = false;
1539bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            for (int i = 0; i < name.length(); i++) {
1540bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                final char c = name.charAt(i);
1541bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                if (isValid(c)) {
1542bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    safeName.append(c);
1543bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                } else {
1544bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    changed = true;
1545bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    safeName.append('_');
1546bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                }
1547bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1548bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (changed) {
1549bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                Log.v(TAG, "changed invalid name '" + name + "' to '" + safeName + "'");
1550bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                name = safeName.toString();
1551bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(name);
1552bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1553bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            mTempName = name;
1554bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1555bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // Must update system property for the cases where dumpstate finishes
1556bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // while the user is still entering other fields (like title or
1557bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            // description)
155885ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme            setBugreportNameProperty(mPid, name);
1559bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1560bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1561bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme       /**
1562bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * Notifies the dialog that the bugreport has finished so it disables the {@code name}
1563bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * field.
1564bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * <p>Once the bugreport is finished dumpstate has already generated the final files, so
1565bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * changing the name would have no effect.
1566bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1567fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        private void onBugreportFinished(int id) {
1568bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            if (mInfoName != null) {
1569bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setEnabled(false);
1570bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                mInfoName.setText(mSavedName);
1571bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme            }
1572bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        }
1573bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1574bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme    }
1575bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
157669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    /**
157746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme     * Information about a bugreport process while its in progress.
157869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme     */
1579c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme    private static final class BugreportInfo implements Parcelable {
1580719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        private final Context context;
1581719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme
158269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
1583fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme         * Sequential, user-friendly id used to identify the bugreport.
1584fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme         */
1585fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        final int id;
1586fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme
1587fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        /**
158846d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * {@code pid} of the {@code dumpstate} process generating the bugreport.
158969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
159069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        final int pid;
159169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
159269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
159346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Name of the bugreport, will be used to rename the final files.
159469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * <p>
159546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Initial value is the bugreport filename reported by {@code dumpstate}, but user can
159669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * change it later to a more meaningful name.
159769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
1598719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        String name;
159969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
160069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
1601bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * User-provided, one-line summary of the bug; when set, will be used as the subject
1602bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
1603bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1604bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        String title;
1605bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1606bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
1607bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * User-provided, detailed description of the bugreport; when set, will be added to the body
1608bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
1609bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme         */
1610bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        String description;
1611bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme
1612bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme        /**
16133fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme         * Maximum progress of the bugreport generation as displayed by the UI.
161469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
1615719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme        int max;
161669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
161769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
16183fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme         * Current progress of the bugreport generation as displayed by the UI.
161969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
162069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        int progress;
162169c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
162269c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        /**
16233fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme         * Maximum progress of the bugreport generation as reported by dumpstate.
16243fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme         */
16253fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        int realMax;
16263fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme
16273fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        /**
16283fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme         * Current progress of the bugreport generation as reported by dumpstate.
16293fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme         */
16303fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        int realProgress;
16313fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme
16323fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme        /**
163369c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         * Time of the last progress update.
163469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme         */
163569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        long lastUpdate = System.currentTimeMillis();
163669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
163746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
1638c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme         * Time of the last progress update when Parcel was created.
1639c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme         */
1640c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        String formattedLastUpdate;
1641c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1642c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        /**
164346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Path of the main bugreport file.
164446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
164546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        File bugreportFile;
164646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
164746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
1648d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Path of the screenshot files.
164946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1650d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        List<File> screenshotFiles = new ArrayList<>(1);
165146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
165246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
165346d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Whether dumpstate sent an intent informing it has finished.
165446d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
165546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        boolean finished;
165646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
165746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
165818b5892950b7f21e66c9268129323cbc0e865699Felipe Leme         * Whether the details entries have been added to the bugreport yet.
165918b5892950b7f21e66c9268129323cbc0e865699Felipe Leme         */
166018b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        boolean addingDetailsToZip;
166118b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        boolean addedDetailsToZip;
166218b5892950b7f21e66c9268129323cbc0e865699Felipe Leme
166318b5892950b7f21e66c9268129323cbc0e865699Felipe Leme        /**
1664d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Internal counter used to name screenshot files.
1665d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1666d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        int screenshotCounter;
1667d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1668d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
166946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
167046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1671fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo(Context context, int id, int pid, String name, int max) {
1672719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme            this.context = context;
1673fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            this.id = id;
167469c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.pid = pid;
167569c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.name = name;
167669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            this.max = max;
167769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
167869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
167946d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        /**
168046d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
168146d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         * without a previous call to BUGREPORT_STARTED.
168246d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme         */
1683fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme        BugreportInfo(Context context, int id) {
1684fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            this(context, id, id, null, 0);
168546d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme            this.finished = true;
168646d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme        }
168746d47911ea8bbbe0b8d7a6029b80da6b1eb94393Felipe Leme
1688d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1689d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Gets the name for next screenshot file.
1690d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1691d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        String getPathNextScreenshot() {
1692d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotCounter ++;
1693d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            return "screenshot-" + pid + "-" + screenshotCounter + ".png";
1694d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1695d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1696d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1697d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Saves the location of a taken screenshot so it can be sent out at the end.
1698d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1699d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        void addScreenshot(File screenshot) {
1700d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotFiles.add(screenshot);
1701d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1702d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
1703d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        /**
1704d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         * Rename all screenshots files so that they contain the user-generated name instead of pid.
1705d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme         */
1706d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        void renameScreenshots(File screenshotDir) {
1707d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            if (TextUtils.isEmpty(name)) {
1708d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                return;
1709d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
1710d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
1711d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            for (File oldFile : screenshotFiles) {
1712d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final String oldName = oldFile.getName();
171385ae3cf46ad66d71e5a29a93e89a0f569d74288bFelipe Leme                final String newName = oldName.replaceFirst(Integer.toString(pid), name);
1714d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                final File newFile;
1715d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                if (!newName.equals(oldName)) {
1716d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    final File renamedFile = new File(screenshotDir, newName);
17174f663f6c25488a83a438a01dfef8372aa4e6b2aaFelipe Leme                    Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
1718d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
1719d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                } else {
1720d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
1721d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    newFile = oldFile;
1722d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                }
1723d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                renamedFiles.add(newFile);
1724d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            }
1725d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme            screenshotFiles = renamedFiles;
1726d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme        }
1727d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme
172869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        String getFormattedLastUpdate() {
1729c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            if (context == null) {
1730c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                // Restored from Parcel
1731c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return formattedLastUpdate == null ?
1732c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                        Long.toString(lastUpdate) : formattedLastUpdate;
1733c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1734719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme            return DateUtils.formatDateTime(context, lastUpdate,
1735719aaae3c167c2b15525dbe5c7db514a2c0c8269Felipe Leme                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
173669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
173769c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme
173869c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        @Override
173969c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        public String toString() {
174069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme            final float percent = ((float) progress * 100 / max);
17413fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            final float realPercent = ((float) realProgress * 100 / realMax);
1742fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            return "id: " + id + ", pid: " + pid + ", name: " + name + ", finished: " + finished
1743bc73ffc06fd2b5b30802cc7e8874a986626b897dFelipe Leme                    + "\n\ttitle: " + title + "\n\tdescription: " + description
1744d1e0f12979441733753b538611f6d73e5527c43cFelipe Leme                    + "\n\tfile: " + bugreportFile + "\n\tscreenshots: " + screenshotFiles
1745510e922e47fec69839dd48c5473540f93d79a508Felipe Leme                    + "\n\tprogress: " + progress + "/" + max + " (" + percent + ")"
17463fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme                    + "\n\treal progress: " + realProgress + "/" + realMax + " (" + realPercent + ")"
174718b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + "\n\tlast_update: " + getFormattedLastUpdate()
174818b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + "\naddingDetailsToZip: " + addingDetailsToZip
174918b5892950b7f21e66c9268129323cbc0e865699Felipe Leme                    + " addedDetailsToZip: " + addedDetailsToZip;
175069c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme        }
1751c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1752c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        // Parcelable contract
1753c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        protected BugreportInfo(Parcel in) {
1754c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            context = null;
1755fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            id = in.readInt();
1756c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            pid = in.readInt();
1757c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            name = in.readString();
1758c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            title = in.readString();
1759c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            description = in.readString();
1760c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            max = in.readInt();
1761c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            progress = in.readInt();
17623fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            realMax = in.readInt();
17633fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            realProgress = in.readInt();
1764c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            lastUpdate = in.readLong();
1765c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            formattedLastUpdate = in.readString();
1766c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            bugreportFile = readFile(in);
1767c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1768c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            int screenshotSize = in.readInt();
1769c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            for (int i = 1; i <= screenshotSize; i++) {
1770c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                  screenshotFiles.add(readFile(in));
1771c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1772c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1773c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            finished = in.readInt() == 1;
1774c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            screenshotCounter = in.readInt();
1775c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1776c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1777c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        @Override
1778c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public void writeToParcel(Parcel dest, int flags) {
1779fd8ea077c0ded14002c32ee346df4fa22a30625aFelipe Leme            dest.writeInt(id);
1780c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(pid);
1781c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(name);
1782c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(title);
1783c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(description);
1784c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(max);
1785c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(progress);
17863fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            dest.writeInt(realMax);
17873fc44b9a6274254e8d3a53b6b1e245c5f9177229Felipe Leme            dest.writeInt(realProgress);
1788c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeLong(lastUpdate);
1789c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(getFormattedLastUpdate());
1790c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            writeFile(dest, bugreportFile);
1791c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1792c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(screenshotFiles.size());
1793c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            for (File screenshotFile : screenshotFiles) {
1794c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                writeFile(dest, screenshotFile);
1795c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1796c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1797c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(finished ? 1 : 0);
1798c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeInt(screenshotCounter);
1799c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1800c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1801c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        @Override
1802c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public int describeContents() {
1803c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return 0;
1804c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1805c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1806c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        private void writeFile(Parcel dest, File file) {
1807c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            dest.writeString(file == null ? null : file.getPath());
1808c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1809c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1810c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        private File readFile(Parcel in) {
1811c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            final String path = in.readString();
1812c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            return path == null ? null : new File(path);
1813c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        }
1814c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1815c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        public static final Parcelable.Creator<BugreportInfo> CREATOR =
1816c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                new Parcelable.Creator<BugreportInfo>() {
1817c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            public BugreportInfo createFromParcel(Parcel source) {
1818c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return new BugreportInfo(source);
1819c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1820c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
1821c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            public BugreportInfo[] newArray(int size) {
1822c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme                return new BugreportInfo[size];
1823c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme            }
1824c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme        };
1825c4f6467702e308844ef0769ba17dcb7b7b32a9e6Felipe Leme
182669c0292affe8be51e10afb2dbf58f0133918a2c3Felipe Leme    }
1827b9238b37838d653c38ce4e712421adb61978fc22Felipe Leme}
1828