blob: a3c2cd8e7a5d7d102a1ea98bf619a510a837f3be [file] [log] [blame]
Nandana Dutt3386fb72018-12-12 17:26:57 +00001/*
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
17package android.os;
18
Nandana Dutt28d8dd72019-01-25 14:31:05 +000019import android.annotation.CallbackExecutor;
Abhijeet Kaur0bbb9f62019-03-08 11:00:29 +000020import android.annotation.FloatRange;
Nandana Dutt3386fb72018-12-12 17:26:57 +000021import android.annotation.IntDef;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.annotation.RequiresPermission;
Nandana Dutt2083e8c2019-01-23 20:02:29 +000025import android.annotation.SystemApi;
Nandana Dutt3386fb72018-12-12 17:26:57 +000026import android.annotation.SystemService;
Nandana Duttd7e8d5a2019-04-11 17:27:45 +010027import android.annotation.TestApi;
Paul Chang9082c392019-11-25 16:43:27 +080028import android.app.ActivityManager;
Nandana Dutt3386fb72018-12-12 17:26:57 +000029import android.content.Context;
Abhijeet Kaur86311f82019-03-29 17:04:48 +000030import android.util.Log;
Nandana Dutt551906c2019-01-23 09:51:49 +000031
32import com.android.internal.util.Preconditions;
Nandana Dutt3386fb72018-12-12 17:26:57 +000033
Nandana Dutt9d6ff292019-03-13 16:42:17 +000034import libcore.io.IoUtils;
35
Abhijeet Kaur86311f82019-03-29 17:04:48 +000036import java.io.File;
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +010037import java.io.FileNotFoundException;
Nandana Dutt3386fb72018-12-12 17:26:57 +000038import java.lang.annotation.Retention;
39import java.lang.annotation.RetentionPolicy;
Nandana Dutt28d8dd72019-01-25 14:31:05 +000040import java.util.concurrent.Executor;
Nandana Dutt3386fb72018-12-12 17:26:57 +000041
42/**
43 * Class that provides a privileged API to capture and consume bugreports.
44 *
45 * @hide
46 */
Nandana Dutt2083e8c2019-01-23 20:02:29 +000047@SystemApi
Nandana Duttd7e8d5a2019-04-11 17:27:45 +010048@TestApi
Nandana Dutt3386fb72018-12-12 17:26:57 +000049@SystemService(Context.BUGREPORT_SERVICE)
Abhijeet Kaur0bbb9f62019-03-08 11:00:29 +000050public final class BugreportManager {
Abhijeet Kaur86311f82019-03-29 17:04:48 +000051
52 private static final String TAG = "BugreportManager";
53
Nandana Dutt3386fb72018-12-12 17:26:57 +000054 private final Context mContext;
55 private final IDumpstate mBinder;
56
57 /** @hide */
58 public BugreportManager(@NonNull Context context, IDumpstate binder) {
59 mContext = context;
60 mBinder = binder;
61 }
62
63 /**
Nandana Dutt28d8dd72019-01-25 14:31:05 +000064 * An interface describing the callback for bugreport progress and status.
Nandana Dutt3386fb72018-12-12 17:26:57 +000065 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +000066 public abstract static class BugreportCallback {
67 /** @hide */
68 @Retention(RetentionPolicy.SOURCE)
69 @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
70 BUGREPORT_ERROR_INVALID_INPUT,
71 BUGREPORT_ERROR_RUNTIME,
72 BUGREPORT_ERROR_USER_DENIED_CONSENT,
Nandana Duttcfb3d482019-02-20 11:25:35 +000073 BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
74 BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS
Nandana Dutt28d8dd72019-01-25 14:31:05 +000075 })
76
77 /** Possible error codes taking a bugreport can encounter */
78 public @interface BugreportErrorCode {}
79
80 /** The input options were invalid */
81 public static final int BUGREPORT_ERROR_INVALID_INPUT =
82 IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
83
84 /** A runtime error occured */
85 public static final int BUGREPORT_ERROR_RUNTIME =
86 IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
87
88 /** User denied consent to share the bugreport */
89 public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
90 IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
91
92 /** The request to get user consent timed out. */
93 public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
94 IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
95
Nandana Duttcfb3d482019-02-20 11:25:35 +000096 /** There is currently a bugreport running. The caller should try again later. */
97 public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS =
98 IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS;
99
Nandana Dutt3386fb72018-12-12 17:26:57 +0000100 /**
101 * Called when there is a progress update.
102 * @param progress the progress in [0.0, 100.0]
103 */
Abhijeet Kaur0bbb9f62019-03-08 11:00:29 +0000104 public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {}
Nandana Duttbba7e822019-01-23 19:11:01 +0000105
Nandana Dutt3386fb72018-12-12 17:26:57 +0000106 /**
107 * Called when taking bugreport resulted in an error.
108 *
Nandana Duttbba7e822019-01-23 19:11:01 +0000109 * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
110 * consent to sharing the bugreport with the calling app.
111 *
112 * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed
113 * out, but the bugreport could be available in the internal directory of dumpstate for
114 * manual retrieval.
Nandana Duttcfb3d482019-02-20 11:25:35 +0000115 *
116 * <p> If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the
117 * caller should try later, as only one bugreport can be in progress at a time.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000118 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000119 public void onError(@BugreportErrorCode int errorCode) {}
Nandana Dutt3386fb72018-12-12 17:26:57 +0000120
121 /**
Nandana Duttb2da22a2019-01-23 08:39:05 +0000122 * Called when taking bugreport finishes successfully.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000123 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000124 public void onFinished() {}
Nandana Dutt3386fb72018-12-12 17:26:57 +0000125 }
126
127 /**
Nandana Duttb2da22a2019-01-23 08:39:05 +0000128 * Starts a bugreport.
129 *
130 * <p>This starts a bugreport in the background. However the call itself can take several
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000131 * seconds to return in the worst case. {@code callback} will receive progress and status
Nandana Duttb2da22a2019-01-23 08:39:05 +0000132 * updates.
133 *
134 * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
135 * user consents to sharing with the calling app.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000136 *
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000137 * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
138 *
Nandana Dutt3386fb72018-12-12 17:26:57 +0000139 * @param bugreportFd file to write the bugreport. This should be opened in write-only,
140 * append mode.
141 * @param screenshotFd file to write the screenshot, if necessary. This should be opened
142 * in write-only, append mode.
143 * @param params options that specify what kind of a bugreport should be taken
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000144 * @param callback callback for progress and status updates
Nandana Dutt3386fb72018-12-12 17:26:57 +0000145 */
146 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000147 public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd,
148 @Nullable ParcelFileDescriptor screenshotFd,
149 @NonNull BugreportParams params,
150 @NonNull @CallbackExecutor Executor executor,
151 @NonNull BugreportCallback callback) {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000152 try {
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000153 Preconditions.checkNotNull(bugreportFd);
154 Preconditions.checkNotNull(params);
155 Preconditions.checkNotNull(executor);
156 Preconditions.checkNotNull(callback);
157
Abhijeet Kaur86311f82019-03-29 17:04:48 +0000158 if (screenshotFd == null) {
159 // Binder needs a valid File Descriptor to be passed
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +0100160 screenshotFd = ParcelFileDescriptor.open(new File("/dev/null"),
Abhijeet Kaur86311f82019-03-29 17:04:48 +0000161 ParcelFileDescriptor.MODE_READ_ONLY);
162 }
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +0100163 DumpstateListener dsListener = new DumpstateListener(executor, callback);
Nandana Dutt161a4462019-01-16 18:18:38 +0000164 // Note: mBinder can get callingUid from the binder transaction.
165 mBinder.startBugreport(-1 /* callingUid */,
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000166 mContext.getOpPackageName(),
Nandana Dutt551906c2019-01-23 09:51:49 +0000167 bugreportFd.getFileDescriptor(),
Abhijeet Kaur86311f82019-03-29 17:04:48 +0000168 screenshotFd.getFileDescriptor(),
Nandana Dutt161a4462019-01-16 18:18:38 +0000169 params.getMode(), dsListener);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000170 } catch (RemoteException e) {
171 throw e.rethrowFromSystemServer();
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +0100172 } catch (FileNotFoundException e) {
173 Log.wtf(TAG, "Not able to find /dev/null file: ", e);
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000174 } finally {
175 // We can close the file descriptors here because binder would have duped them.
176 IoUtils.closeQuietly(bugreportFd);
177 if (screenshotFd != null) {
178 IoUtils.closeQuietly(screenshotFd);
179 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000180 }
181 }
182
Nandana Duttb2da22a2019-01-23 08:39:05 +0000183 /*
184 * Cancels a currently running bugreport.
185 */
186 @RequiresPermission(android.Manifest.permission.DUMP)
187 public void cancelBugreport() {
188 try {
189 mBinder.cancelBugreport();
190 } catch (RemoteException e) {
191 throw e.rethrowFromSystemServer();
192 }
193 }
194
Paul Chang9082c392019-11-25 16:43:27 +0800195 /**
196 * Requests a bugreport.
197 *
198 * <p>This requests the platform/system to take a bugreport and makes the final bugreport
199 * available to the user. The user may choose to share it with another app, but the bugreport
200 * is never given back directly to the app that requested it.
201 *
202 * @param params {@link BugreportParams} that specify what kind of a bugreport should
203 * be taken, please note that not all kinds of bugreport allow for a
204 * progress notification
205 * @param shareTitle title on the final share notification
206 * @param shareDescription description on the final share notification
207 */
208 @RequiresPermission(android.Manifest.permission.DUMP)
209 public void requestBugreport(@NonNull BugreportParams params, @Nullable CharSequence shareTitle,
210 @Nullable CharSequence shareDescription) {
211 try {
212 String title = shareTitle == null ? null : shareTitle.toString();
213 String description = shareDescription == null ? null : shareDescription.toString();
214 ActivityManager.getService().requestBugReportWithDescription(title, description,
215 params.getMode());
216 } catch (RemoteException e) {
217 throw e.rethrowFromSystemServer();
218 }
219 }
220
Nandana Dutt551906c2019-01-23 09:51:49 +0000221 private final class DumpstateListener extends IDumpstateListener.Stub {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000222 private final Executor mExecutor;
223 private final BugreportCallback mCallback;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000224
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +0100225 DumpstateListener(Executor executor, BugreportCallback callback) {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000226 mExecutor = executor;
227 mCallback = callback;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000228 }
229
230 @Override
Nandana Dutt432f8c72019-01-14 17:39:13 +0000231 public void onProgress(int progress) throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000232 final long identity = Binder.clearCallingIdentity();
233 try {
234 mExecutor.execute(() -> {
235 mCallback.onProgress(progress);
236 });
237 } finally {
238 Binder.restoreCallingIdentity(identity);
239 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000240 }
241
242 @Override
243 public void onError(int errorCode) throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000244 final long identity = Binder.clearCallingIdentity();
245 try {
246 mExecutor.execute(() -> {
247 mCallback.onError(errorCode);
248 });
249 } finally {
250 Binder.restoreCallingIdentity(identity);
251 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000252 }
253
254 @Override
Nandana Duttb2da22a2019-01-23 08:39:05 +0000255 public void onFinished() throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000256 final long identity = Binder.clearCallingIdentity();
Nandana Duttb2da22a2019-01-23 08:39:05 +0000257 try {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000258 mExecutor.execute(() -> {
259 mCallback.onFinished();
260 });
Nandana Duttb2da22a2019-01-23 08:39:05 +0000261 } finally {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000262 Binder.restoreCallingIdentity(identity);
Nandana Duttb2da22a2019-01-23 08:39:05 +0000263 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000264 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000265 }
266}