blob: a1c7b08b8ba37681f09bbacd6a01c132c1312090 [file] [log] [blame]
Joe Onorato1754d742016-11-21 17:51:35 -08001/*
2 * Copyright (C) 2016 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
Joe Onorato8daca752019-01-28 19:41:58 -080019import android.annotation.IntDef;
Joe Onoratoe21ab7e2018-12-18 15:00:25 -080020import android.annotation.NonNull;
Joe Onorato8daca752019-01-28 19:41:58 -080021import android.annotation.Nullable;
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060022import android.annotation.RequiresPermission;
Joe Onorato1754d742016-11-21 17:51:35 -080023import android.annotation.SystemApi;
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060024import android.annotation.SystemService;
Joe Onorato1754d742016-11-21 17:51:35 -080025import android.annotation.TestApi;
26import android.content.Context;
Joe Onoratoe21ab7e2018-12-18 15:00:25 -080027import android.net.Uri;
Joe Onorato1754d742016-11-21 17:51:35 -080028import android.util.Slog;
29
Joe Onorato8daca752019-01-28 19:41:58 -080030import java.io.Closeable;
31import java.io.IOException;
32import java.io.InputStream;
33import java.lang.annotation.Retention;
34import java.lang.annotation.RetentionPolicy;
Joe Onoratoe21ab7e2018-12-18 15:00:25 -080035import java.util.ArrayList;
36import java.util.List;
37
Joe Onorato1754d742016-11-21 17:51:35 -080038/**
39 * Class to take an incident report.
40 *
41 * @hide
42 */
43@SystemApi
44@TestApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060045@SystemService(Context.INCIDENT_SERVICE)
Joe Onorato1754d742016-11-21 17:51:35 -080046public class IncidentManager {
Yi Jin3ec5cc72018-01-26 13:42:43 -080047 private static final String TAG = "IncidentManager";
Joe Onorato1754d742016-11-21 17:51:35 -080048
Joe Onoratoe21ab7e2018-12-18 15:00:25 -080049 /**
50 * Authority for pending report id urls.
51 *
52 * @hide
53 */
54 public static final String URI_SCHEME = "content";
55
56 /**
57 * Authority for pending report id urls.
58 *
59 * @hide
60 */
61 public static final String URI_AUTHORITY = "android.os.IncidentManager";
62
63 /**
64 * Authority for pending report id urls.
65 *
66 * @hide
67 */
68 public static final String URI_PATH = "/pending";
69
70 /**
71 * Query parameter for the uris for the pending report id.
72 *
73 * @hide
74 */
75 public static final String URI_PARAM_ID = "id";
76
77 /**
78 * Query parameter for the uris for the pending report id.
79 *
80 * @hide
81 */
82 public static final String URI_PARAM_CALLING_PACKAGE = "pkg";
83
84 /**
85 * Query parameter for the uris for the pending report id, in wall clock
86 * ({@link System.currentTimeMillis()}) timebase.
87 *
88 * @hide
89 */
90 public static final String URI_PARAM_TIMESTAMP = "t";
91
92 /**
93 * Query parameter for the uris for the pending report id.
94 *
95 * @hide
96 */
97 public static final String URI_PARAM_FLAGS = "flags";
98
99 /**
100 * Do the confirmation with a dialog instead of the default, which is a notification.
101 * It is possible for the dialog to be downgraded to a notification in some cases.
102 */
103 public static final int FLAG_CONFIRMATION_DIALOG = 0x1;
104
Joe Onorato8daca752019-01-28 19:41:58 -0800105 /**
106 * Flag marking fields and incident reports than can be taken
107 * off the device only via adb.
108 */
109 public static final int PRIVACY_POLICY_LOCAL = 0;
110
111 /**
112 * Flag marking fields and incident reports than can be taken
113 * off the device with contemporary consent.
114 */
115 public static final int PRIVACY_POLICY_EXPLICIT = 100;
116
117 /**
118 * Flag marking fields and incident reports than can be taken
119 * off the device with prior consent.
120 */
121 public static final int PRIVACY_POLICY_AUTO = 200;
122
123 /** @hide */
124 @IntDef(flag = false, prefix = { "PRIVACY_POLICY_" }, value = {
125 PRIVACY_POLICY_AUTO,
126 PRIVACY_POLICY_EXPLICIT,
127 PRIVACY_POLICY_LOCAL,
128 })
129 @Retention(RetentionPolicy.SOURCE)
130 public @interface PrivacyPolicy {}
131
Yi Jinf32af482017-08-11 15:00:49 -0700132 private final Context mContext;
Joe Onorato1754d742016-11-21 17:51:35 -0800133
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800134 private Object mLock = new Object();
135 private IIncidentManager mIncidentService;
136 private IIncidentCompanion mCompanionService;
137
138 /**
139 * Record for a report that has been taken and is pending user authorization
140 * to share it.
141 * @hide
142 */
143 @SystemApi
144 @TestApi
145 public static class PendingReport {
146 /**
147 * Encoded data.
148 */
149 private final Uri mUri;
150
151 /**
152 * URI_PARAM_FLAGS from the uri
153 */
154 private final int mFlags;
155
156 /**
157 * URI_PARAM_CALLING_PACKAGE from the uri
158 */
159 private final String mRequestingPackage;
160
161 /**
162 * URI_PARAM_TIMESTAMP from the uri
163 */
164 private final long mTimestamp;
165
166 /**
167 * Constructor.
168 */
169 public PendingReport(@NonNull Uri uri) {
170 int flags = 0;
171 try {
172 flags = Integer.parseInt(uri.getQueryParameter(URI_PARAM_FLAGS));
173 } catch (NumberFormatException ex) {
174 throw new RuntimeException("Invalid URI: No " + URI_PARAM_FLAGS
175 + " parameter. " + uri);
176 }
177 mFlags = flags;
178
179 String requestingPackage = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
180 if (requestingPackage == null) {
181 throw new RuntimeException("Invalid URI: No " + URI_PARAM_CALLING_PACKAGE
182 + " parameter. " + uri);
183 }
184 mRequestingPackage = requestingPackage;
185
186 long timestamp = -1;
187 try {
188 timestamp = Long.parseLong(uri.getQueryParameter(URI_PARAM_TIMESTAMP));
189 } catch (NumberFormatException ex) {
190 throw new RuntimeException("Invalid URI: No " + URI_PARAM_TIMESTAMP
191 + " parameter. " + uri);
192 }
193 mTimestamp = timestamp;
194
195 mUri = uri;
196 }
197
198 /**
199 * Get the package with which this report will be shared.
200 */
201 public @NonNull String getRequestingPackage() {
202 return mRequestingPackage;
203 }
204
205 /**
206 * Get the flags requested for this pending report.
207 *
208 * @see #FLAG_CONFIRMATION_DIALOG
209 */
210 public int getFlags() {
211 return mFlags;
212 }
213
214 /**
215 * Get the time this pending report was posted.
216 */
217 public long getTimestamp() {
218 return mTimestamp;
219 }
220
221 /**
222 * Get the URI associated with this PendingReport. It can be used to
223 * re-retrieve it from {@link IncidentManager} or set as the data field of
224 * an Intent.
225 */
226 public @NonNull Uri getUri() {
227 return mUri;
228 }
229
230 /**
231 * String representation of this PendingReport.
232 */
233 @Override
234 public @NonNull String toString() {
235 return "PendingReport(" + getUri().toString() + ")";
236 }
237 }
238
239 /**
Joe Onorato8daca752019-01-28 19:41:58 -0800240 * Record of an incident report that has previously been taken.
241 * @hide
242 */
243 @SystemApi
244 @TestApi
245 public static class IncidentReport implements Parcelable, Closeable {
246 private final long mTimestampMs;
247 private final int mPrivacyPolicy;
248 private ParcelFileDescriptor mFileDescriptor;
249
250 public IncidentReport(Parcel in) {
251 mTimestampMs = in.readLong();
252 mPrivacyPolicy = in.readInt();
253 if (in.readInt() != 0) {
254 mFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
255 } else {
256 mFileDescriptor = null;
257 }
258 }
259
260 /**
261 * Close the input stream associated with this entry.
262 */
263 public void close() {
264 try {
265 if (mFileDescriptor != null) {
266 mFileDescriptor.close();
267 mFileDescriptor = null;
268 }
269 } catch (IOException e) {
270 }
271 }
272
273 /**
274 * Get the time at which this incident report was taken, in wall clock time
275 * ({@link System#uptimeMillis System.uptimeMillis()} time base).
276 */
277 public long getTimestamp() {
278 return mTimestampMs;
279 }
280
281 /**
282 * Get the privacy level to which this report has been filtered.
283 *
284 * @see #PRIVACY_POLICY_AUTO
285 * @see #PRIVACY_POLICY_EXPLICIT
286 * @see #PRIVACY_POLICY_LOCAL
287 */
288 public long getPrivacyPolicy() {
289 return mPrivacyPolicy;
290 }
291
292 /**
293 * Get the contents of this incident report.
294 */
295 public InputStream getInputStream() throws IOException {
296 if (mFileDescriptor == null) {
297 return null;
298 }
299 return new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
300 }
301
302 /**
303 * @inheritDoc
304 */
305 public int describeContents() {
306 return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
307 }
308
309 /**
310 * @inheritDoc
311 */
312 public void writeToParcel(Parcel out, int flags) {
313 out.writeLong(mTimestampMs);
314 out.writeInt(mPrivacyPolicy);
315 if (mFileDescriptor != null) {
316 out.writeInt(1);
317 mFileDescriptor.writeToParcel(out, flags);
318 } else {
319 out.writeInt(0);
320 }
321 }
322
323 /**
324 * {@link Parcelable.Creator Creator} for {@link IncidentReport}.
325 */
326 public static final Parcelable.Creator<IncidentReport> CREATOR = new Parcelable.Creator() {
327 /**
328 * @inheritDoc
329 */
330 public IncidentReport[] newArray(int size) {
331 return new IncidentReport[size];
332 }
333
334 /**
335 * @inheritDoc
336 */
337 public IncidentReport createFromParcel(Parcel in) {
338 return new IncidentReport(in);
339 }
340 };
341 }
342
343 /**
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800344 * Listener for the status of an incident report being authroized or denied.
345 *
346 * @see #requestAuthorization
347 * @see #cancelAuthorization
348 */
349 public static class AuthListener {
350 IIncidentAuthListener.Stub mBinder = new IIncidentAuthListener.Stub() {
351 @Override
352 public void onReportApproved() {
353 AuthListener.this.onReportApproved();
354 }
355
356 @Override
357 public void onReportDenied() {
358 AuthListener.this.onReportDenied();
359 }
360 };
361
362 /**
363 * Called when a report is approved.
364 */
365 public void onReportApproved() {
366 }
367
368 /**
369 * Called when a report is denied.
370 */
371 public void onReportDenied() {
372 }
373 }
Yi Jin3ec5cc72018-01-26 13:42:43 -0800374
Joe Onorato1754d742016-11-21 17:51:35 -0800375 /**
376 * @hide
377 */
378 public IncidentManager(Context context) {
379 mContext = context;
380 }
381
382 /**
Joe Onorato8daca752019-01-28 19:41:58 -0800383 * Take an incident report.
Joe Onorato1754d742016-11-21 17:51:35 -0800384 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600385 @RequiresPermission(allOf = {
386 android.Manifest.permission.DUMP,
387 android.Manifest.permission.PACKAGE_USAGE_STATS
388 })
Joe Onorato1754d742016-11-21 17:51:35 -0800389 public void reportIncident(IncidentReportArgs args) {
Yi Jinf32af482017-08-11 15:00:49 -0700390 reportIncidentInternal(args);
Joe Onorato1754d742016-11-21 17:51:35 -0800391 }
392
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800393 /**
394 * Request authorization of an incident report.
395 */
396 @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
397 public void requestAuthorization(int callingUid, String callingPackage, int flags,
398 AuthListener listener) {
399 try {
400 getCompanionServiceLocked().authorizeReport(callingUid, callingPackage, flags,
401 listener.mBinder);
402 } catch (RemoteException ex) {
403 // System process going down
404 throw new RuntimeException(ex);
405 }
406 }
407
408 /**
409 * Cancel a previous request for incident report authorization.
410 */
411 @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
412 public void cancelAuthorization(AuthListener listener) {
413 try {
414 getCompanionServiceLocked().cancelAuthorization(listener.mBinder);
415 } catch (RemoteException ex) {
416 // System process going down
417 throw new RuntimeException(ex);
418 }
419 }
420
421 /**
422 * Get incident (and bug) reports that are pending approval to share.
423 */
424 @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
425 public List<PendingReport> getPendingReports() {
426 List<String> strings;
427 try {
428 strings = getCompanionServiceLocked().getPendingReports();
429 } catch (RemoteException ex) {
430 throw new RuntimeException(ex);
431 }
432 final int size = strings.size();
433 ArrayList<PendingReport> result = new ArrayList(size);
434 for (int i = 0; i < size; i++) {
435 result.add(new PendingReport(Uri.parse(strings.get(i))));
436 }
437 return result;
438 }
439
440 /**
441 * Allow this report to be shared with the given app.
442 */
443 @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
444 public void approveReport(Uri uri) {
445 try {
446 getCompanionServiceLocked().approveReport(uri.toString());
447 } catch (RemoteException ex) {
448 // System process going down
449 throw new RuntimeException(ex);
450 }
451 }
452
453 /**
454 * Do not allow this report to be shared with the given app.
455 */
456 @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
457 public void denyReport(Uri uri) {
458 try {
459 getCompanionServiceLocked().denyReport(uri.toString());
460 } catch (RemoteException ex) {
461 // System process going down
462 throw new RuntimeException(ex);
Joe Onorato1754d742016-11-21 17:51:35 -0800463 }
Yi Jin3ec5cc72018-01-26 13:42:43 -0800464 }
Joe Onorato1754d742016-11-21 17:51:35 -0800465
Joe Onorato8daca752019-01-28 19:41:58 -0800466 /**
467 * Get the incident reports that are available for upload for the supplied
468 * broadcast recevier.
469 *
470 * @param receiverClass Class name of broadcast receiver in this package that
471 * was registered to retrieve reports.
472 *
473 * @return A list of {@link Uri Uris} that are awaiting upload.
474 */
475 @RequiresPermission(allOf = {
476 android.Manifest.permission.DUMP,
477 android.Manifest.permission.PACKAGE_USAGE_STATS
478 })
479 public @NonNull List<Uri> getIncidentReportList(String receiverClass) {
480 throw new RuntimeException("implement me");
481 }
482
483 /**
484 * Get the incident report with the given URI id.
485 *
486 * @param uri Identifier of the incident report.
487 *
488 * @return an IncidentReport object, or null if the incident report has been
489 * expired from disk.
490 */
491 @RequiresPermission(allOf = {
492 android.Manifest.permission.DUMP,
493 android.Manifest.permission.PACKAGE_USAGE_STATS
494 })
495 public @Nullable IncidentReport getIncidentReport(Uri uri) {
496 throw new RuntimeException("implement me");
497 }
498
499 /**
500 * Delete the incident report with the given URI id.
501 *
502 * @param uri Identifier of the incident report.
503 */
504 @RequiresPermission(allOf = {
505 android.Manifest.permission.DUMP,
506 android.Manifest.permission.PACKAGE_USAGE_STATS
507 })
508 public void deleteIncidentReports(Uri uri) {
509 throw new RuntimeException("implement me");
510 }
511
Yi Jin3ec5cc72018-01-26 13:42:43 -0800512 private void reportIncidentInternal(IncidentReportArgs args) {
Joe Onorato1754d742016-11-21 17:51:35 -0800513 try {
Yi Jin3ec5cc72018-01-26 13:42:43 -0800514 final IIncidentManager service = getIIncidentManagerLocked();
515 if (service == null) {
516 Slog.e(TAG, "reportIncident can't find incident binder service");
517 return;
518 }
Joe Onorato1754d742016-11-21 17:51:35 -0800519 service.reportIncident(args);
520 } catch (RemoteException ex) {
521 Slog.e(TAG, "reportIncident failed", ex);
522 }
523 }
Yi Jin3ec5cc72018-01-26 13:42:43 -0800524
525 private IIncidentManager getIIncidentManagerLocked() throws RemoteException {
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800526 if (mIncidentService != null) {
527 return mIncidentService;
Yi Jin3ec5cc72018-01-26 13:42:43 -0800528 }
529
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800530 synchronized (mLock) {
531 if (mIncidentService != null) {
532 return mIncidentService;
Yi Jin3ec5cc72018-01-26 13:42:43 -0800533 }
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800534 mIncidentService = IIncidentManager.Stub.asInterface(
Yi Jin3ec5cc72018-01-26 13:42:43 -0800535 ServiceManager.getService(Context.INCIDENT_SERVICE));
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800536 if (mIncidentService != null) {
537 mIncidentService.asBinder().linkToDeath(() -> {
538 synchronized (mLock) {
539 mIncidentService = null;
540 }
541 }, 0);
Yi Jin3ec5cc72018-01-26 13:42:43 -0800542 }
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800543 return mIncidentService;
Yi Jin3ec5cc72018-01-26 13:42:43 -0800544 }
545 }
546
Joe Onoratoe21ab7e2018-12-18 15:00:25 -0800547 private IIncidentCompanion getCompanionServiceLocked() throws RemoteException {
548 if (mCompanionService != null) {
549 return mCompanionService;
550 }
551
552 synchronized (this) {
553 if (mCompanionService != null) {
554 return mCompanionService;
555 }
556 mCompanionService = IIncidentCompanion.Stub.asInterface(
557 ServiceManager.getService(Context.INCIDENT_COMPANION_SERVICE));
558 if (mCompanionService != null) {
559 mCompanionService.asBinder().linkToDeath(() -> {
560 synchronized (mLock) {
561 mCompanionService = null;
562 }
563 }, 0);
564 }
565 return mCompanionService;
566 }
567 }
Joe Onorato1754d742016-11-21 17:51:35 -0800568}
569