1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os;
18 
19 import android.Manifest;
20 import android.annotation.CallbackExecutor;
21 import android.annotation.FloatRange;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SuppressAutoDoc;
27 import android.annotation.SystemApi;
28 import android.annotation.SystemService;
29 import android.annotation.UserHandleAware;
30 import android.annotation.WorkerThread;
31 import android.app.ActivityManager;
32 import android.content.Context;
33 import android.util.Log;
34 import android.widget.Toast;
35 
36 import com.android.internal.R;
37 import com.android.internal.util.Preconditions;
38 
39 import libcore.io.IoUtils;
40 
41 import java.io.File;
42 import java.io.FileNotFoundException;
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.util.concurrent.Executor;
46 
47 /**
48  * Class that provides a privileged API to capture and consume bugreports.
49  *
50  * <p>This class may only be used by apps that currently have carrier privileges (see {@link
51  * android.telephony.TelephonyManager#hasCarrierPrivileges}) on an active SIM or priv-apps
52  * explicitly allowed by the device manufacturer.
53  *
54  * <p>Only one bugreport can be generated by the system at a time.
55  */
56 @SystemService(Context.BUGREPORT_SERVICE)
57 public final class BugreportManager {
58 
59     private static final String TAG = "BugreportManager";
60 
61     private final Context mContext;
62     private final IDumpstate mBinder;
63 
64     /** @hide */
BugreportManager(@onNull Context context, IDumpstate binder)65     public BugreportManager(@NonNull Context context, IDumpstate binder) {
66         mContext = context;
67         mBinder = binder;
68     }
69 
70     /**
71      * An interface describing the callback for bugreport progress and status.
72      *
73      * <p>Callers will receive {@link #onProgress} calls as the bugreport progresses, followed by a
74      * terminal call to either {@link #onFinished} or {@link #onError}.
75      *
76      * <p>If an issue is encountered while starting the bugreport asynchronously, callers will
77      * receive an {@link #onError} call without any {@link #onProgress} callbacks.
78      */
79     public abstract static class BugreportCallback {
80         /**
81          * Possible error codes taking a bugreport can encounter.
82          *
83          * @hide
84          */
85         @Retention(RetentionPolicy.SOURCE)
86         @IntDef(
87                 prefix = {"BUGREPORT_ERROR_"},
88                 value = {
89                     BUGREPORT_ERROR_INVALID_INPUT,
90                     BUGREPORT_ERROR_RUNTIME,
91                     BUGREPORT_ERROR_USER_DENIED_CONSENT,
92                     BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
93                     BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS,
94                     BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE
95                 })
96         public @interface BugreportErrorCode {}
97 
98         /**
99          * The input options were invalid. For example, the destination file the app provided could
100          * not be written by the system.
101          */
102         public static final int BUGREPORT_ERROR_INVALID_INPUT =
103                 IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
104 
105         /** A runtime error occurred. */
106         public static final int BUGREPORT_ERROR_RUNTIME =
107                 IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
108 
109         /** User denied consent to share the bugreport. */
110         public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
111                 IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
112 
113         /** The request to get user consent timed out. */
114         public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
115                 IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
116 
117         /** There is currently a bugreport running. The caller should try again later. */
118         public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS =
119                 IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS;
120 
121         /** There is no bugreport to retrieve for the caller. */
122         public static final int BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE =
123                 IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE;
124 
125         /**
126          * Called when there is a progress update.
127          *
128          * @param progress the progress in [0.0, 100.0]
129          */
onProgress(@loatRangefrom = 0f, to = 100f) float progress)130         public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {}
131 
132         /**
133          * Called when taking bugreport resulted in an error.
134          *
135          * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
136          * consent to sharing the bugreport with the calling app.
137          *
138          * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed
139          * out, but the bugreport could be available in the internal directory of dumpstate for
140          * manual retrieval.
141          *
142          * <p>If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the caller
143          * should try later, as only one bugreport can be in progress at a time.
144          */
onError(@ugreportErrorCode int errorCode)145         public void onError(@BugreportErrorCode int errorCode) {}
146 
147         /**
148          * Called when taking bugreport finishes successfully.
149          *
150          * <p>This callback will be invoked if the
151          * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.
152          */
onFinished()153         public void onFinished() {}
154 
155         /** Called when taking bugreport finishes successfully.
156          *
157          * <p>This callback will only be invoked if the
158          * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is set. Otherwise, the
159          * {@link #onFinished()} callback will be invoked.
160          *
161          * @param bugreportFile the absolute path of the generated bugreport file.
162          * @hide
163 
164          */
165         @SystemApi
onFinished(@onNull String bugreportFile)166         public void onFinished(@NonNull String bugreportFile) {}
167 
168         /**
169          * Called when it is ready for calling app to show UI, showing any extra UI before this
170          * callback can interfere with bugreport generation.
171          */
onEarlyReportFinished()172         public void onEarlyReportFinished() {}
173     }
174 
175     /**
176      * Speculatively pre-dumps UI data for a bugreport request that might come later.
177      *
178      * <p>Triggers the dump of certain critical UI data, e.g. traces stored in short
179      * ring buffers that might get lost by the time the actual bugreport is requested.
180      *
181      * <p>{@link #startBugreport} will then pick the pre-dumped data if both of the following
182      * conditions are met:
183      * - {@link android.os.BugreportParams#BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA} is specified.
184      * - {@link #preDumpUiData} and {@link #startBugreport} were called by the same UID.
185      * @hide
186      */
187     @SystemApi
188     @RequiresPermission(android.Manifest.permission.DUMP)
189     @WorkerThread
preDumpUiData()190     public void preDumpUiData() {
191         try {
192             mBinder.preDumpUiData(mContext.getOpPackageName());
193         } catch (RemoteException e) {
194             throw e.rethrowFromSystemServer();
195         }
196     }
197 
198     /**
199      * Starts a bugreport.
200      *
201      * <p>This starts a bugreport in the background. However the call itself can take several
202      * seconds to return in the worst case. {@code callback} will receive progress and status
203      * updates.
204      *
205      * <p>The bugreport artifacts will be copied over to the given file descriptors only if the user
206      * consents to sharing with the calling app. If
207      * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} is set, user consent will be deferred
208      * and no files will be copied to the given file descriptors.
209      *
210      * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
211      *
212      * @param bugreportFd file to write the bugreport. This should be opened in write-only, append
213      *     mode.
214      * @param screenshotFd file to write the screenshot, if necessary. This should be opened in
215      *     write-only, append mode.
216      * @param params options that specify what kind of a bugreport should be taken
217      * @param callback callback for progress and status updates
218      * @hide
219      */
220     @SystemApi
221     @RequiresPermission(android.Manifest.permission.DUMP)
222     @WorkerThread
startBugreport( @onNull ParcelFileDescriptor bugreportFd, @Nullable ParcelFileDescriptor screenshotFd, @NonNull BugreportParams params, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback)223     public void startBugreport(
224             @NonNull ParcelFileDescriptor bugreportFd,
225             @Nullable ParcelFileDescriptor screenshotFd,
226             @NonNull BugreportParams params,
227             @NonNull @CallbackExecutor Executor executor,
228             @NonNull BugreportCallback callback) {
229         try {
230             Preconditions.checkNotNull(bugreportFd);
231             Preconditions.checkNotNull(params);
232             Preconditions.checkNotNull(executor);
233             Preconditions.checkNotNull(callback);
234 
235             boolean deferConsent =
236                     (params.getFlags() & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
237             boolean isScreenshotRequested = screenshotFd != null || deferConsent;
238             if (screenshotFd == null) {
239                 // Binder needs a valid File Descriptor to be passed
240                 screenshotFd =
241                         ParcelFileDescriptor.open(
242                                 new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY);
243             }
244             DumpstateListener dsListener =
245                     new DumpstateListener(executor, callback, isScreenshotRequested, deferConsent);
246             // Note: mBinder can get callingUid from the binder transaction.
247             mBinder.startBugreport(
248                     -1 /* callingUid */,
249                     mContext.getOpPackageName(),
250                     bugreportFd.getFileDescriptor(),
251                     screenshotFd.getFileDescriptor(),
252                     params.getMode(),
253                     params.getFlags(),
254                     dsListener,
255                     isScreenshotRequested,
256                     /* skipUserConsent = */ false);
257         } catch (RemoteException e) {
258             throw e.rethrowFromSystemServer();
259         } catch (FileNotFoundException e) {
260             Log.wtf(TAG, "Not able to find /dev/null file: ", e);
261         } finally {
262             // We can close the file descriptors here because binder would have duped them.
263             IoUtils.closeQuietly(bugreportFd);
264             if (screenshotFd != null) {
265                 IoUtils.closeQuietly(screenshotFd);
266             }
267         }
268     }
269 
270     /**
271      * Retrieves a previously generated bugreport.
272      *
273      * <p>The previously generated bugreport must have been generated by calling {@link
274      * #startBugreport(ParcelFileDescriptor, ParcelFileDescriptor, BugreportParams,
275      * Executor, BugreportCallback)} with the {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT}
276      * flag set. The bugreport file returned by the {@link BugreportCallback#onFinished(String)}
277      * callback for a previously generated bugreport must be passed to this method. A caller may
278      * only retrieve bugreports that they have previously requested.
279      *
280      * <p>The bugreport artifacts will be copied over to the given file descriptor only if the user
281      * consents to sharing with the calling app.
282      *
283      * <p>{@link BugreportManager} takes ownership of {@code bugreportFd}.
284      *
285      * <p>The caller can reattempt to retrieve the bugreport multiple times if the user has not
286      * consented on previous attempts.
287      *
288      * @param bugreportFile the identifier for a bugreport that was previously generated for this
289      *      caller using {@code startBugreport}.
290      * @param bugreportFd file to copy over the previous bugreport. This should be opened in
291      *      write-only, append mode.
292      * @param executor the executor to execute callback methods.
293      * @param callback callback for progress and status updates.
294      * @hide
295      */
296     @SystemApi
297     @RequiresPermission(Manifest.permission.DUMP)
298     @WorkerThread
299     @UserHandleAware
retrieveBugreport( @onNull String bugreportFile, @NonNull ParcelFileDescriptor bugreportFd, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback )300     public void retrieveBugreport(
301             @NonNull String bugreportFile,
302             @NonNull ParcelFileDescriptor bugreportFd,
303             @NonNull @CallbackExecutor Executor executor,
304             @NonNull BugreportCallback callback
305     ) {
306         try {
307             Preconditions.checkNotNull(bugreportFile);
308             Preconditions.checkNotNull(bugreportFd);
309             Preconditions.checkNotNull(executor);
310             Preconditions.checkNotNull(callback);
311             DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false);
312             mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(),
313                     mContext.getUserId(),
314                     bugreportFd.getFileDescriptor(),
315                     bugreportFile,
316                     /* keepBugreportOnRetrieval = */ false,
317                     /* skipUserConsent = */ false,
318                     dsListener);
319         } catch (RemoteException e) {
320             throw e.rethrowFromSystemServer();
321         } finally {
322             IoUtils.closeQuietly(bugreportFd);
323         }
324     }
325 
326     /**
327      * Starts a connectivity bugreport.
328      *
329      * <p>The connectivity bugreport is a specialized version of bugreport that only includes
330      * information specifically for debugging connectivity-related issues (e.g. telephony, wi-fi,
331      * and IP networking issues). It is intended primarily for use by OEMs and network providers
332      * such as mobile network operators. In addition to generally excluding information that isn't
333      * targeted to connectivity debugging, this type of bugreport excludes PII and sensitive
334      * information that isn't strictly necessary for connectivity debugging.
335      *
336      * <p>The calling app MUST have a context-specific reason for requesting a connectivity
337      * bugreport, such as detecting a connectivity-related issue. This API SHALL NOT be used to
338      * perform random sampling from a fleet of public end-user devices.
339      *
340      * <p>Calling this API will cause the system to ask the user for consent every single time. The
341      * bugreport artifacts will be copied over to the given file descriptors only if the user
342      * consents to sharing with the calling app.
343      *
344      * <p>This starts a bugreport in the background. However the call itself can take several
345      * seconds to return in the worst case. {@code callback} will receive progress and status
346      * updates.
347      *
348      * <p>Requires that the calling app has carrier privileges (see {@link
349      * android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription.
350      *
351      * @param bugreportFd file to write the bugreport. This should be opened in write-only, append
352      *     mode.
353      * @param callback callback for progress and status updates.
354      */
355     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
356     @WorkerThread
startConnectivityBugreport( @onNull ParcelFileDescriptor bugreportFd, @NonNull @CallbackExecutor Executor executor, @NonNull BugreportCallback callback)357     public void startConnectivityBugreport(
358             @NonNull ParcelFileDescriptor bugreportFd,
359             @NonNull @CallbackExecutor Executor executor,
360             @NonNull BugreportCallback callback) {
361         startBugreport(
362                 bugreportFd,
363                 null /* screenshotFd */,
364                 new BugreportParams(BugreportParams.BUGREPORT_MODE_TELEPHONY),
365                 executor,
366                 callback);
367     }
368 
369     /**
370      * Cancels the currently running bugreport.
371      *
372      * <p>Apps are only able to cancel their own bugreports. App A cannot cancel a bugreport started
373      * by app B.
374      *
375      * <p>Requires permission: {@link android.Manifest.permission#DUMP} or that the calling app has
376      * carrier privileges (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on
377      * any active subscription.
378      *
379      * @throws SecurityException if trying to cancel another app's bugreport in progress
380      */
381     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
382     @WorkerThread
cancelBugreport()383     public void cancelBugreport() {
384         try {
385             mBinder.cancelBugreport(-1 /* callingUid */, mContext.getOpPackageName());
386         } catch (RemoteException e) {
387             throw e.rethrowFromSystemServer();
388         }
389     }
390 
391     /**
392      * Requests a bugreport.
393      *
394      * <p>This requests the platform/system to take a bugreport and makes the final bugreport
395      * available to the user. The user may choose to share it with another app, but the bugreport is
396      * never given back directly to the app that requested it.
397      *
398      * @param params {@link BugreportParams} that specify what kind of a bugreport should be taken,
399      *     please note that not all kinds of bugreport allow for a progress notification
400      * @param shareTitle title on the final share notification
401      * @param shareDescription description on the final share notification
402      * @hide
403      */
404     @SystemApi
405     @RequiresPermission(Manifest.permission.DUMP)
requestBugreport( @onNull BugreportParams params, @Nullable CharSequence shareTitle, @Nullable CharSequence shareDescription)406     public void requestBugreport(
407             @NonNull BugreportParams params,
408             @Nullable CharSequence shareTitle,
409             @Nullable CharSequence shareDescription) {
410         try {
411             String title = shareTitle == null ? null : shareTitle.toString();
412             String description = shareDescription == null ? null : shareDescription.toString();
413             ActivityManager.getService()
414                     .requestBugReportWithDescription(title, description, params.getMode());
415         } catch (RemoteException e) {
416             throw e.rethrowFromSystemServer();
417         }
418     }
419 
420     private final class DumpstateListener extends IDumpstateListener.Stub {
421         private final Executor mExecutor;
422         private final BugreportCallback mCallback;
423         private final boolean mIsScreenshotRequested;
424         private final boolean mIsConsentDeferred;
425 
DumpstateListener( Executor executor, BugreportCallback callback, boolean isScreenshotRequested, boolean isConsentDeferred)426         DumpstateListener(
427                 Executor executor, BugreportCallback callback, boolean isScreenshotRequested,
428                 boolean isConsentDeferred) {
429             mExecutor = executor;
430             mCallback = callback;
431             mIsScreenshotRequested = isScreenshotRequested;
432             mIsConsentDeferred = isConsentDeferred;
433         }
434 
435         @Override
onProgress(int progress)436         public void onProgress(int progress) throws RemoteException {
437             final long identity = Binder.clearCallingIdentity();
438             try {
439                 mExecutor.execute(() -> mCallback.onProgress(progress));
440             } finally {
441                 Binder.restoreCallingIdentity(identity);
442             }
443         }
444 
445         @Override
onError(int errorCode)446         public void onError(int errorCode) throws RemoteException {
447             final long identity = Binder.clearCallingIdentity();
448             try {
449                 mExecutor.execute(() -> mCallback.onError(errorCode));
450             } finally {
451                 Binder.restoreCallingIdentity(identity);
452             }
453         }
454 
455         @Override
onFinished(String bugreportFile)456         public void onFinished(String bugreportFile) throws RemoteException {
457             final long identity = Binder.clearCallingIdentity();
458             try {
459                 if (mIsConsentDeferred) {
460                     mExecutor.execute(() -> mCallback.onFinished(bugreportFile));
461                 } else {
462                     mExecutor.execute(() -> mCallback.onFinished());
463                 }
464             } finally {
465                 Binder.restoreCallingIdentity(identity);
466             }
467         }
468 
469         @Override
onScreenshotTaken(boolean success)470         public void onScreenshotTaken(boolean success) throws RemoteException {
471             if (!mIsScreenshotRequested) {
472                 return;
473             }
474 
475             Handler mainThreadHandler = new Handler(Looper.getMainLooper());
476             mainThreadHandler.post(
477                     () -> {
478                         int message =
479                                 success
480                                         ? R.string.bugreport_screenshot_success_toast
481                                         : R.string.bugreport_screenshot_failure_toast;
482                         Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
483                     });
484         }
485 
486         @Override
onUiIntensiveBugreportDumpsFinished()487         public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
488             final long identity = Binder.clearCallingIdentity();
489             try {
490                 mExecutor.execute(() -> mCallback.onEarlyReportFinished());
491             } finally {
492                 Binder.restoreCallingIdentity(identity);
493             }
494         }
495     }
496 }
497