blob: f87abde361f30e76f71de78944c4f98708cd1cd5 [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;
27import android.content.Context;
Nandana Dutt551906c2019-01-23 09:51:49 +000028
29import com.android.internal.util.Preconditions;
Nandana Dutt3386fb72018-12-12 17:26:57 +000030
Nandana Dutt9d6ff292019-03-13 16:42:17 +000031import libcore.io.IoUtils;
32
Nandana Dutt3386fb72018-12-12 17:26:57 +000033import java.io.FileDescriptor;
34import java.lang.annotation.Retention;
35import java.lang.annotation.RetentionPolicy;
Nandana Dutt28d8dd72019-01-25 14:31:05 +000036import java.util.concurrent.Executor;
Nandana Dutt3386fb72018-12-12 17:26:57 +000037
38/**
39 * Class that provides a privileged API to capture and consume bugreports.
40 *
41 * @hide
42 */
Nandana Dutt2083e8c2019-01-23 20:02:29 +000043@SystemApi
Nandana Dutt3386fb72018-12-12 17:26:57 +000044@SystemService(Context.BUGREPORT_SERVICE)
Abhijeet Kaur0bbb9f62019-03-08 11:00:29 +000045public final class BugreportManager {
Nandana Dutt3386fb72018-12-12 17:26:57 +000046 private final Context mContext;
47 private final IDumpstate mBinder;
48
49 /** @hide */
50 public BugreportManager(@NonNull Context context, IDumpstate binder) {
51 mContext = context;
52 mBinder = binder;
53 }
54
55 /**
Nandana Dutt28d8dd72019-01-25 14:31:05 +000056 * An interface describing the callback for bugreport progress and status.
Nandana Dutt3386fb72018-12-12 17:26:57 +000057 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +000058 public abstract static class BugreportCallback {
59 /** @hide */
60 @Retention(RetentionPolicy.SOURCE)
61 @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
62 BUGREPORT_ERROR_INVALID_INPUT,
63 BUGREPORT_ERROR_RUNTIME,
64 BUGREPORT_ERROR_USER_DENIED_CONSENT,
Nandana Duttcfb3d482019-02-20 11:25:35 +000065 BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
66 BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS
Nandana Dutt28d8dd72019-01-25 14:31:05 +000067 })
68
69 /** Possible error codes taking a bugreport can encounter */
70 public @interface BugreportErrorCode {}
71
72 /** The input options were invalid */
73 public static final int BUGREPORT_ERROR_INVALID_INPUT =
74 IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
75
76 /** A runtime error occured */
77 public static final int BUGREPORT_ERROR_RUNTIME =
78 IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
79
80 /** User denied consent to share the bugreport */
81 public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT =
82 IDumpstateListener.BUGREPORT_ERROR_USER_DENIED_CONSENT;
83
84 /** The request to get user consent timed out. */
85 public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT =
86 IDumpstateListener.BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT;
87
Nandana Duttcfb3d482019-02-20 11:25:35 +000088 /** There is currently a bugreport running. The caller should try again later. */
89 public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS =
90 IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS;
91
Nandana Dutt3386fb72018-12-12 17:26:57 +000092 /**
93 * Called when there is a progress update.
94 * @param progress the progress in [0.0, 100.0]
95 */
Abhijeet Kaur0bbb9f62019-03-08 11:00:29 +000096 public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {}
Nandana Duttbba7e822019-01-23 19:11:01 +000097
Nandana Dutt3386fb72018-12-12 17:26:57 +000098 /**
99 * Called when taking bugreport resulted in an error.
100 *
Nandana Duttbba7e822019-01-23 19:11:01 +0000101 * <p>If {@code BUGREPORT_ERROR_USER_DENIED_CONSENT} is passed, then the user did not
102 * consent to sharing the bugreport with the calling app.
103 *
104 * <p>If {@code BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT} is passed, then the consent timed
105 * out, but the bugreport could be available in the internal directory of dumpstate for
106 * manual retrieval.
Nandana Duttcfb3d482019-02-20 11:25:35 +0000107 *
108 * <p> If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the
109 * caller should try later, as only one bugreport can be in progress at a time.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000110 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000111 public void onError(@BugreportErrorCode int errorCode) {}
Nandana Dutt3386fb72018-12-12 17:26:57 +0000112
113 /**
Nandana Duttb2da22a2019-01-23 08:39:05 +0000114 * Called when taking bugreport finishes successfully.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000115 */
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000116 public void onFinished() {}
Nandana Dutt3386fb72018-12-12 17:26:57 +0000117 }
118
119 /**
Nandana Duttb2da22a2019-01-23 08:39:05 +0000120 * Starts a bugreport.
121 *
122 * <p>This starts a bugreport in the background. However the call itself can take several
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000123 * seconds to return in the worst case. {@code callback} will receive progress and status
Nandana Duttb2da22a2019-01-23 08:39:05 +0000124 * updates.
125 *
126 * <p>The bugreport artifacts will be copied over to the given file descriptors only if the
127 * user consents to sharing with the calling app.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000128 *
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000129 * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
130 *
Nandana Dutt3386fb72018-12-12 17:26:57 +0000131 * @param bugreportFd file to write the bugreport. This should be opened in write-only,
132 * append mode.
133 * @param screenshotFd file to write the screenshot, if necessary. This should be opened
134 * in write-only, append mode.
135 * @param params options that specify what kind of a bugreport should be taken
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000136 * @param callback callback for progress and status updates
Nandana Dutt3386fb72018-12-12 17:26:57 +0000137 */
138 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000139 public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd,
140 @Nullable ParcelFileDescriptor screenshotFd,
141 @NonNull BugreportParams params,
142 @NonNull @CallbackExecutor Executor executor,
143 @NonNull BugreportCallback callback) {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000144 try {
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000145 Preconditions.checkNotNull(bugreportFd);
146 Preconditions.checkNotNull(params);
147 Preconditions.checkNotNull(executor);
148 Preconditions.checkNotNull(callback);
149
150 DumpstateListener dsListener = new DumpstateListener(executor, callback);
Nandana Dutt161a4462019-01-16 18:18:38 +0000151 // Note: mBinder can get callingUid from the binder transaction.
152 mBinder.startBugreport(-1 /* callingUid */,
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000153 mContext.getOpPackageName(),
Nandana Dutt551906c2019-01-23 09:51:49 +0000154 bugreportFd.getFileDescriptor(),
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000155 (screenshotFd != null
156 ? screenshotFd.getFileDescriptor() : new FileDescriptor()),
Nandana Dutt161a4462019-01-16 18:18:38 +0000157 params.getMode(), dsListener);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000158 } catch (RemoteException e) {
159 throw e.rethrowFromSystemServer();
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000160 } finally {
161 // We can close the file descriptors here because binder would have duped them.
162 IoUtils.closeQuietly(bugreportFd);
163 if (screenshotFd != null) {
164 IoUtils.closeQuietly(screenshotFd);
165 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000166 }
167 }
168
Nandana Duttb2da22a2019-01-23 08:39:05 +0000169 /*
170 * Cancels a currently running bugreport.
171 */
172 @RequiresPermission(android.Manifest.permission.DUMP)
173 public void cancelBugreport() {
174 try {
175 mBinder.cancelBugreport();
176 } catch (RemoteException e) {
177 throw e.rethrowFromSystemServer();
178 }
179 }
180
Nandana Dutt551906c2019-01-23 09:51:49 +0000181 private final class DumpstateListener extends IDumpstateListener.Stub {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000182 private final Executor mExecutor;
183 private final BugreportCallback mCallback;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000184
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000185 DumpstateListener(Executor executor, BugreportCallback callback) {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000186 mExecutor = executor;
187 mCallback = callback;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000188 }
189
190 @Override
Nandana Dutt432f8c72019-01-14 17:39:13 +0000191 public void onProgress(int progress) throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000192 final long identity = Binder.clearCallingIdentity();
193 try {
194 mExecutor.execute(() -> {
195 mCallback.onProgress(progress);
196 });
197 } finally {
198 Binder.restoreCallingIdentity(identity);
199 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000200 }
201
202 @Override
203 public void onError(int errorCode) throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000204 final long identity = Binder.clearCallingIdentity();
205 try {
206 mExecutor.execute(() -> {
207 mCallback.onError(errorCode);
208 });
209 } finally {
210 Binder.restoreCallingIdentity(identity);
211 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000212 }
213
214 @Override
Nandana Duttb2da22a2019-01-23 08:39:05 +0000215 public void onFinished() throws RemoteException {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000216 final long identity = Binder.clearCallingIdentity();
Nandana Duttb2da22a2019-01-23 08:39:05 +0000217 try {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000218 mExecutor.execute(() -> {
219 mCallback.onFinished();
220 });
Nandana Duttb2da22a2019-01-23 08:39:05 +0000221 } finally {
Nandana Dutt28d8dd72019-01-25 14:31:05 +0000222 Binder.restoreCallingIdentity(identity);
Nandana Duttb2da22a2019-01-23 08:39:05 +0000223 }
Nandana Dutt432f8c72019-01-14 17:39:13 +0000224 }
225
226 // Old methods; should go away
227 @Override
Nandana Dutt3386fb72018-12-12 17:26:57 +0000228 public void onProgressUpdated(int progress) throws RemoteException {
Nandana Duttc8c77bc2019-01-15 12:48:12 +0000229 // TODO(b/111441001): remove from interface
Nandana Dutt3386fb72018-12-12 17:26:57 +0000230 }
231
232 @Override
233 public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
Nandana Duttc8c77bc2019-01-15 12:48:12 +0000234 // TODO(b/111441001): remove from interface
Nandana Dutt3386fb72018-12-12 17:26:57 +0000235 }
236
237 @Override
238 public void onSectionComplete(String title, int status, int size, int durationMs)
239 throws RemoteException {
Nandana Duttc8c77bc2019-01-15 12:48:12 +0000240 // TODO(b/111441001): remove from interface
Nandana Dutt3386fb72018-12-12 17:26:57 +0000241 }
242 }
243}