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