blob: c5cbad33c06c786a416790624e14820f2cca21e0 [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;
Nandana Dutt3386fb72018-12-12 17:26:57 +000028import android.content.Context;
Abhijeet Kaur86311f82019-03-29 17:04:48 +000029import android.util.Log;
Nandana Dutt551906c2019-01-23 09:51:49 +000030
31import com.android.internal.util.Preconditions;
Nandana Dutt3386fb72018-12-12 17:26:57 +000032
Nandana Dutt9d6ff292019-03-13 16:42:17 +000033import libcore.io.IoUtils;
34
Abhijeet Kaur86311f82019-03-29 17:04:48 +000035import java.io.File;
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +010036import java.io.FileNotFoundException;
Nandana Dutt3386fb72018-12-12 17:26:57 +000037import java.lang.annotation.Retention;
38import java.lang.annotation.RetentionPolicy;
Nandana Dutt28d8dd72019-01-25 14:31:05 +000039import java.util.concurrent.Executor;
Nandana Dutt3386fb72018-12-12 17:26:57 +000040
41/**
42 * Class that provides a privileged API to capture and consume bugreports.
43 *
44 * @hide
45 */
Nandana Dutt2083e8c2019-01-23 20:02:29 +000046@SystemApi
Nandana Duttd7e8d5a2019-04-11 17:27:45 +010047@TestApi
Nandana Dutt3386fb72018-12-12 17:26:57 +000048@SystemService(Context.BUGREPORT_SERVICE)
Abhijeet Kaur0bbb9f62019-03-08 11:00:29 +000049public final class BugreportManager {
Abhijeet Kaur86311f82019-03-29 17:04:48 +000050
51 private static final String TAG = "BugreportManager";
52
Nandana Dutt3386fb72018-12-12 17:26:57 +000053 private final Context mContext;
54 private final IDumpstate mBinder;
55
56 /** @hide */
57 public BugreportManager(@NonNull Context context, IDumpstate binder) {
58 mContext = context;
59 mBinder = binder;
60 }
61
62 /**
Nandana Dutt28d8dd72019-01-25 14:31:05 +000063 * An interface describing the callback for bugreport progress and status.
Nandana Dutt3386fb72018-12-12 17:26:57 +000064 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +000065 public abstract static class BugreportCallback {
66 /** @hide */
67 @Retention(RetentionPolicy.SOURCE)
68 @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
69 BUGREPORT_ERROR_INVALID_INPUT,
70 BUGREPORT_ERROR_RUNTIME,
71 BUGREPORT_ERROR_USER_DENIED_CONSENT,
Nandana Duttcfb3d482019-02-20 11:25:35 +000072 BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
73 BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS
Nandana Dutt28d8dd72019-01-25 14:31:05 +000074 })
75
76 /** Possible error codes taking a bugreport can encounter */
77 public @interface BugreportErrorCode {}
78
79 /** The input options were invalid */
80 public static final int BUGREPORT_ERROR_INVALID_INPUT =
81 IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
82
83 /** A runtime error occured */
84 public static final int BUGREPORT_ERROR_RUNTIME =
85 IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
86
87 /** User denied consent to share the bugreport */
88 public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
89 IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
90
91 /** The request to get user consent timed out. */
92 public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
93 IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
94
Nandana Duttcfb3d482019-02-20 11:25:35 +000095 /** There is currently a bugreport running. The caller should try again later. */
96 public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS =
97 IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS;
98
Nandana Dutt3386fb72018-12-12 17:26:57 +000099 /**
100 * Called when there is a progress update.
101 * @param progress the progress in [0.0, 100.0]
102 */
Abhijeet Kaur0bbb9f62019-03-08 11:00:29 +0000103 public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {}
Nandana Duttbba7e822019-01-23 19:11:01 +0000104
Nandana Dutt3386fb72018-12-12 17:26:57 +0000105 /**
106 * Called when taking bugreport resulted in an error.
107 *
Nandana Duttbba7e822019-01-23 19:11:01 +0000108 * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
109 * consent to sharing the bugreport with the calling app.
110 *
111 * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed
112 * out, but the bugreport could be available in the internal directory of dumpstate for
113 * manual retrieval.
Nandana Duttcfb3d482019-02-20 11:25:35 +0000114 *
115 * <p> If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the
116 * caller should try later, as only one bugreport can be in progress at a time.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000117 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000118 public void onError(@BugreportErrorCode int errorCode) {}
Nandana Dutt3386fb72018-12-12 17:26:57 +0000119
120 /**
Nandana Duttb2da22a2019-01-23 08:39:05 +0000121 * Called when taking bugreport finishes successfully.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000122 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000123 public void onFinished() {}
Nandana Dutt3386fb72018-12-12 17:26:57 +0000124 }
125
126 /**
Nandana Duttb2da22a2019-01-23 08:39:05 +0000127 * Starts a bugreport.
128 *
129 * <p>This starts a bugreport in the background. However the call itself can take several
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000130 * seconds to return in the worst case. {@code callback} will receive progress and status
Nandana Duttb2da22a2019-01-23 08:39:05 +0000131 * updates.
132 *
133 * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
134 * user consents to sharing with the calling app.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000135 *
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000136 * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
137 *
Nandana Dutt3386fb72018-12-12 17:26:57 +0000138 * @param bugreportFd file to write the bugreport. This should be opened in write-only,
139 * append mode.
140 * @param screenshotFd file to write the screenshot, if necessary. This should be opened
141 * in write-only, append mode.
142 * @param params options that specify what kind of a bugreport should be taken
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000143 * @param callback callback for progress and status updates
Nandana Dutt3386fb72018-12-12 17:26:57 +0000144 */
145 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000146 public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd,
147 @Nullable ParcelFileDescriptor screenshotFd,
148 @NonNull BugreportParams params,
149 @NonNull @CallbackExecutor Executor executor,
150 @NonNull BugreportCallback callback) {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000151 try {
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000152 Preconditions.checkNotNull(bugreportFd);
153 Preconditions.checkNotNull(params);
154 Preconditions.checkNotNull(executor);
155 Preconditions.checkNotNull(callback);
156
Abhijeet Kaur86311f82019-03-29 17:04:48 +0000157 if (screenshotFd == null) {
158 // Binder needs a valid File Descriptor to be passed
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +0100159 screenshotFd = ParcelFileDescriptor.open(new File("/dev/null"),
Abhijeet Kaur86311f82019-03-29 17:04:48 +0000160 ParcelFileDescriptor.MODE_READ_ONLY);
161 }
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +0100162 DumpstateListener dsListener = new DumpstateListener(executor, callback);
Nandana Dutt161a4462019-01-16 18:18:38 +0000163 // Note: mBinder can get callingUid from the binder transaction.
164 mBinder.startBugreport(-1 /* callingUid */,
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000165 mContext.getOpPackageName(),
Nandana Dutt551906c2019-01-23 09:51:49 +0000166 bugreportFd.getFileDescriptor(),
Abhijeet Kaur86311f82019-03-29 17:04:48 +0000167 screenshotFd.getFileDescriptor(),
Nandana Dutt161a4462019-01-16 18:18:38 +0000168 params.getMode(), dsListener);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000169 } catch (RemoteException e) {
170 throw e.rethrowFromSystemServer();
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +0100171 } catch (FileNotFoundException e) {
172 Log.wtf(TAG, "Not able to find /dev/null file: ", e);
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000173 } finally {
174 // We can close the file descriptors here because binder would have duped them.
175 IoUtils.closeQuietly(bugreportFd);
176 if (screenshotFd != null) {
177 IoUtils.closeQuietly(screenshotFd);
178 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000179 }
180 }
181
Nandana Duttb2da22a2019-01-23 08:39:05 +0000182 /*
183 * Cancels a currently running bugreport.
184 */
185 @RequiresPermission(android.Manifest.permission.DUMP)
186 public void cancelBugreport() {
187 try {
188 mBinder.cancelBugreport();
189 } catch (RemoteException e) {
190 throw e.rethrowFromSystemServer();
191 }
192 }
193
Nandana Dutt551906c2019-01-23 09:51:49 +0000194 private final class DumpstateListener extends IDumpstateListener.Stub {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000195 private final Executor mExecutor;
196 private final BugreportCallback mCallback;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000197
Abhijeet Kaur006a6bb2019-04-16 18:28:20 +0100198 DumpstateListener(Executor executor, BugreportCallback callback) {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000199 mExecutor = executor;
200 mCallback = callback;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000201 }
202
203 @Override
Nandana Dutt432f8c72019-01-14 17:39:13 +0000204 public void onProgress(int progress) throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000205 final long identity = Binder.clearCallingIdentity();
206 try {
207 mExecutor.execute(() -> {
208 mCallback.onProgress(progress);
209 });
210 } finally {
211 Binder.restoreCallingIdentity(identity);
212 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000213 }
214
215 @Override
216 public void onError(int errorCode) throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000217 final long identity = Binder.clearCallingIdentity();
218 try {
219 mExecutor.execute(() -> {
220 mCallback.onError(errorCode);
221 });
222 } finally {
223 Binder.restoreCallingIdentity(identity);
224 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000225 }
226
227 @Override
Nandana Duttb2da22a2019-01-23 08:39:05 +0000228 public void onFinished() throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000229 final long identity = Binder.clearCallingIdentity();
Nandana Duttb2da22a2019-01-23 08:39:05 +0000230 try {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000231 mExecutor.execute(() -> {
232 mCallback.onFinished();
233 });
Nandana Duttb2da22a2019-01-23 08:39:05 +0000234 } finally {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000235 Binder.restoreCallingIdentity(identity);
Nandana Duttb2da22a2019-01-23 08:39:05 +0000236 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000237 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000238 }
239}