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