blob: 565d31a26dbc7d536b75cc255ce3189b8151e678 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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
19import android.annotation.CallbackExecutor;
20import android.annotation.IntDef;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.RequiresPermission;
24import android.annotation.SystemApi;
25import android.annotation.SystemService;
26import android.annotation.TestApi;
27import android.content.Context;
28import android.net.Uri;
29import android.util.Slog;
30
31import java.io.Closeable;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.lang.annotation.Retention;
36import java.lang.annotation.RetentionPolicy;
37import java.util.ArrayList;
38import java.util.List;
39import java.util.Objects;
40import java.util.concurrent.Executor;
41
42/**
43 * Class to take an incident report.
44 *
45 * @hide
46 */
47@SystemApi
48@TestApi
49@SystemService(Context.INCIDENT_SERVICE)
50public class IncidentManager {
51 private static final String TAG = "IncidentManager";
52
53 /**
54 * Authority for pending report id urls.
55 *
56 * @hide
57 */
58 public static final String URI_SCHEME = "content";
59
60 /**
61 * Authority for pending report id urls.
62 *
63 * @hide
64 */
65 public static final String URI_AUTHORITY = "android.os.IncidentManager";
66
67 /**
68 * Authority for pending report id urls.
69 *
70 * @hide
71 */
72 public static final String URI_PATH = "/pending";
73
74 /**
75 * Query parameter for the uris for the pending report id.
76 *
77 * @hide
78 */
79 public static final String URI_PARAM_ID = "id";
80
81 /**
82 * Query parameter for the uris for the incident report id.
83 *
84 * @hide
85 */
86 public static final String URI_PARAM_REPORT_ID = "r";
87
88 /**
89 * Query parameter for the uris for the pending report id.
90 *
91 * @hide
92 */
93 public static final String URI_PARAM_CALLING_PACKAGE = "pkg";
94
95 /**
96 * Query parameter for the uris for the pending report id, in wall clock
97 * ({@link System.currentTimeMillis()}) timebase.
98 *
99 * @hide
100 */
101 public static final String URI_PARAM_TIMESTAMP = "t";
102
103 /**
104 * Query parameter for the uris for the pending report id.
105 *
106 * @hide
107 */
108 public static final String URI_PARAM_FLAGS = "flags";
109
110 /**
111 * Query parameter for the uris for the pending report id.
112 *
113 * @hide
114 */
115 public static final String URI_PARAM_RECEIVER_CLASS = "receiver";
116
117 /**
118 * Do the confirmation with a dialog instead of the default, which is a notification.
119 * It is possible for the dialog to be downgraded to a notification in some cases.
120 */
121 public static final int FLAG_CONFIRMATION_DIALOG = 0x1;
122
123 /**
124 * Flag marking fields and incident reports than can be taken
125 * off the device only via adb.
126 */
127 public static final int PRIVACY_POLICY_LOCAL = 0;
128
129 /**
130 * Flag marking fields and incident reports than can be taken
131 * off the device with contemporary consent.
132 */
133 public static final int PRIVACY_POLICY_EXPLICIT = 100;
134
135 /**
136 * Flag marking fields and incident reports than can be taken
137 * off the device with prior consent.
138 */
139 public static final int PRIVACY_POLICY_AUTO = 200;
140
141 /** @hide */
142 @IntDef(flag = false, prefix = { "PRIVACY_POLICY_" }, value = {
143 PRIVACY_POLICY_AUTO,
144 PRIVACY_POLICY_EXPLICIT,
145 PRIVACY_POLICY_LOCAL,
146 })
147 @Retention(RetentionPolicy.SOURCE)
148 public @interface PrivacyPolicy {}
149
150 private final Context mContext;
151
152 private Object mLock = new Object();
153 private IIncidentManager mIncidentService;
154 private IIncidentCompanion mCompanionService;
155
156 /**
157 * Record for a report that has been taken and is pending user authorization
158 * to share it.
159 * @hide
160 */
161 @SystemApi
162 @TestApi
163 public static class PendingReport {
164 /**
165 * Encoded data.
166 */
167 private final Uri mUri;
168
169 /**
170 * URI_PARAM_FLAGS from the uri
171 */
172 private final int mFlags;
173
174 /**
175 * URI_PARAM_CALLING_PACKAGE from the uri
176 */
177 private final String mRequestingPackage;
178
179 /**
180 * URI_PARAM_TIMESTAMP from the uri
181 */
182 private final long mTimestamp;
183
184 /**
185 * Constructor.
186 */
187 public PendingReport(@NonNull Uri uri) {
188 int flags = 0;
189 try {
190 flags = Integer.parseInt(uri.getQueryParameter(URI_PARAM_FLAGS));
191 } catch (NumberFormatException ex) {
192 throw new RuntimeException("Invalid URI: No " + URI_PARAM_FLAGS
193 + " parameter. " + uri);
194 }
195 mFlags = flags;
196
197 String requestingPackage = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
198 if (requestingPackage == null) {
199 throw new RuntimeException("Invalid URI: No " + URI_PARAM_CALLING_PACKAGE
200 + " parameter. " + uri);
201 }
202 mRequestingPackage = requestingPackage;
203
204 long timestamp = -1;
205 try {
206 timestamp = Long.parseLong(uri.getQueryParameter(URI_PARAM_TIMESTAMP));
207 } catch (NumberFormatException ex) {
208 throw new RuntimeException("Invalid URI: No " + URI_PARAM_TIMESTAMP
209 + " parameter. " + uri);
210 }
211 mTimestamp = timestamp;
212
213 mUri = uri;
214 }
215
216 /**
217 * Get the package with which this report will be shared.
218 */
219 public @NonNull String getRequestingPackage() {
220 return mRequestingPackage;
221 }
222
223 /**
224 * Get the flags requested for this pending report.
225 *
226 * @see #FLAG_CONFIRMATION_DIALOG
227 */
228 public int getFlags() {
229 return mFlags;
230 }
231
232 /**
233 * Get the time this pending report was posted.
234 */
235 public long getTimestamp() {
236 return mTimestamp;
237 }
238
239 /**
240 * Get the URI associated with this PendingReport. It can be used to
241 * re-retrieve it from {@link IncidentManager} or set as the data field of
242 * an Intent.
243 */
244 public @NonNull Uri getUri() {
245 return mUri;
246 }
247
248 /**
249 * String representation of this PendingReport.
250 */
251 @Override
252 public @NonNull String toString() {
253 return "PendingReport(" + getUri().toString() + ")";
254 }
255
256 /**
257 * @inheritDoc
258 */
259 @Override
260 public boolean equals(@Nullable Object obj) {
261 if (this == obj) {
262 return true;
263 }
264 if (!(obj instanceof PendingReport)) {
265 return false;
266 }
267 final PendingReport that = (PendingReport) obj;
268 return this.mUri.equals(that.mUri)
269 && this.mFlags == that.mFlags
270 && this.mRequestingPackage.equals(that.mRequestingPackage)
271 && this.mTimestamp == that.mTimestamp;
272 }
273 }
274
275 /**
276 * Record of an incident report that has previously been taken.
277 * @hide
278 */
279 @SystemApi
280 @TestApi
281 public static class IncidentReport implements Parcelable, Closeable {
282 private final long mTimestampNs;
283 private final int mPrivacyPolicy;
284 private ParcelFileDescriptor mFileDescriptor;
285
286 public IncidentReport(Parcel in) {
287 mTimestampNs = in.readLong();
288 mPrivacyPolicy = in.readInt();
289 if (in.readInt() != 0) {
290 mFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
291 } else {
292 mFileDescriptor = null;
293 }
294 }
295
296 /**
297 * Close the input stream associated with this entry.
298 */
299 public void close() {
300 try {
301 if (mFileDescriptor != null) {
302 mFileDescriptor.close();
303 mFileDescriptor = null;
304 }
305 } catch (IOException e) {
306 }
307 }
308
309 /**
310 * Get the time at which this incident report was taken, in wall clock time
311 * ({@link System#currenttimeMillis System.currenttimeMillis()} time base).
312 */
313 public long getTimestamp() {
314 return mTimestampNs / 1000000;
315 }
316
317 /**
318 * Get the privacy level to which this report has been filtered.
319 *
320 * @see #PRIVACY_POLICY_AUTO
321 * @see #PRIVACY_POLICY_EXPLICIT
322 * @see #PRIVACY_POLICY_LOCAL
323 */
324 public long getPrivacyPolicy() {
325 return mPrivacyPolicy;
326 }
327
328 /**
329 * Get the contents of this incident report.
330 */
331 public InputStream getInputStream() throws IOException {
332 if (mFileDescriptor == null) {
333 return null;
334 }
335 return new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
336 }
337
338 /**
339 * @inheritDoc
340 */
341 public int describeContents() {
342 return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
343 }
344
345 /**
346 * @inheritDoc
347 */
348 public void writeToParcel(Parcel out, int flags) {
349 out.writeLong(mTimestampNs);
350 out.writeInt(mPrivacyPolicy);
351 if (mFileDescriptor != null) {
352 out.writeInt(1);
353 mFileDescriptor.writeToParcel(out, flags);
354 } else {
355 out.writeInt(0);
356 }
357 }
358
359 /**
360 * {@link Parcelable.Creator Creator} for {@link IncidentReport}.
361 */
362 public static final @android.annotation.NonNull Parcelable.Creator<IncidentReport> CREATOR = new Parcelable.Creator() {
363 /**
364 * @inheritDoc
365 */
366 public IncidentReport[] newArray(int size) {
367 return new IncidentReport[size];
368 }
369
370 /**
371 * @inheritDoc
372 */
373 public IncidentReport createFromParcel(Parcel in) {
374 return new IncidentReport(in);
375 }
376 };
377 }
378
379 /**
380 * Listener for the status of an incident report being authorized or denied.
381 *
382 * @see #requestAuthorization
383 * @see #cancelAuthorization
384 */
385 public static class AuthListener {
386 Executor mExecutor;
387
388 IIncidentAuthListener.Stub mBinder = new IIncidentAuthListener.Stub() {
389 @Override
390 public void onReportApproved() {
391 if (mExecutor != null) {
392 mExecutor.execute(() -> {
393 AuthListener.this.onReportApproved();
394 });
395 } else {
396 AuthListener.this.onReportApproved();
397 }
398 }
399
400 @Override
401 public void onReportDenied() {
402 if (mExecutor != null) {
403 mExecutor.execute(() -> {
404 AuthListener.this.onReportDenied();
405 });
406 } else {
407 AuthListener.this.onReportDenied();
408 }
409 }
410 };
411
412 /**
413 * Called when a report is approved.
414 */
415 public void onReportApproved() {
416 }
417
418 /**
419 * Called when a report is denied.
420 */
421 public void onReportDenied() {
422 }
423 }
424
425 /**
426 * Callback for dumping an extended (usually vendor-supplied) incident report section
427 *
428 * @see #registerSection
429 * @see #unregisterSection
430 */
431 public static class DumpCallback {
432 private int mId;
433 private Executor mExecutor;
434
435 IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
436 @Override
437 public void onDumpSection(ParcelFileDescriptor pfd) {
438 if (mExecutor != null) {
439 mExecutor.execute(() -> {
440 DumpCallback.this.onDumpSection(mId,
441 new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
442 });
443 } else {
444 DumpCallback.this.onDumpSection(mId,
445 new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
446 }
447 }
448 };
449
450 /**
451 * Dump the registered section as a protobuf message to the given OutputStream. Called when
452 * incidentd requests to dump this section.
453 *
454 * @param id the id of the registered section. The same id used in calling
455 * {@link #registerSection(int, String, DumpCallback)} will be passed in here.
456 * @param out the OutputStream to write the protobuf message
457 */
458 public void onDumpSection(int id, @NonNull OutputStream out) {
459 }
460 }
461
462 /**
463 * @hide
464 */
465 public IncidentManager(Context context) {
466 mContext = context;
467 }
468
469 /**
470 * Take an incident report.
471 */
472 @RequiresPermission(allOf = {
473 android.Manifest.permission.DUMP,
474 android.Manifest.permission.PACKAGE_USAGE_STATS
475 })
476 public void reportIncident(IncidentReportArgs args) {
477 reportIncidentInternal(args);
478 }
479
480 /**
481 * Request authorization of an incident report.
482 */
483 @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
484 public void requestAuthorization(int callingUid, String callingPackage, int flags,
485 AuthListener listener) {
486 requestAuthorization(callingUid, callingPackage, flags,
487 mContext.getMainExecutor(), listener);
488 }
489
490 /**
491 * Request authorization of an incident report.
492 * @hide
493 */
494 @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
495 public void requestAuthorization(int callingUid, @NonNull String callingPackage, int flags,
496 @NonNull @CallbackExecutor Executor executor, @NonNull AuthListener listener) {
497 try {
498 if (listener.mExecutor != null) {
499 throw new RuntimeException("Do not reuse AuthListener objects when calling"
500 + " requestAuthorization");
501 }
502 listener.mExecutor = executor;
503 getCompanionServiceLocked().authorizeReport(callingUid, callingPackage, null, null,
504 flags, listener.mBinder);
505 } catch (RemoteException ex) {
506 // System process going down
507 throw new RuntimeException(ex);
508 }
509 }
510
511 /**
512 * Cancel a previous request for incident report authorization.
513 */
514 @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
515 public void cancelAuthorization(AuthListener listener) {
516 try {
517 getCompanionServiceLocked().cancelAuthorization(listener.mBinder);
518 } catch (RemoteException ex) {
519 // System process going down
520 throw new RuntimeException(ex);
521 }
522 }
523
524 /**
525 * Get incident (and bug) reports that are pending approval to share.
526 */
527 @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
528 public List<PendingReport> getPendingReports() {
529 List<String> strings;
530 try {
531 strings = getCompanionServiceLocked().getPendingReports();
532 } catch (RemoteException ex) {
533 throw new RuntimeException(ex);
534 }
535 final int size = strings.size();
536 ArrayList<PendingReport> result = new ArrayList(size);
537 for (int i = 0; i < size; i++) {
538 result.add(new PendingReport(Uri.parse(strings.get(i))));
539 }
540 return result;
541 }
542
543 /**
544 * Allow this report to be shared with the given app.
545 */
546 @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
547 public void approveReport(Uri uri) {
548 try {
549 getCompanionServiceLocked().approveReport(uri.toString());
550 } catch (RemoteException ex) {
551 // System process going down
552 throw new RuntimeException(ex);
553 }
554 }
555
556 /**
557 * Do not allow this report to be shared with the given app.
558 */
559 @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS)
560 public void denyReport(Uri uri) {
561 try {
562 getCompanionServiceLocked().denyReport(uri.toString());
563 } catch (RemoteException ex) {
564 // System process going down
565 throw new RuntimeException(ex);
566 }
567 }
568
569 /**
570 * Register a callback to dump an extended incident report section with the given id and name,
571 * running on the supplied executor.
572 *
573 * Calling <code>registerSection</code> with a duplicate id will override previous registration.
574 * However, the request must come from the same calling uid.
575 *
576 * @param id the ID of the extended section. It should be unique system-wide, and be
577 * different from IDs of all existing section in
578 * frameworks/base/core/proto/android/os/incident.proto.
579 * Also see incident.proto for other rules about the ID.
580 * @param name the name to display in logs and/or stderr when taking an incident report
581 * containing this section, mainly for debugging purpose
582 * @param executor the executor used to run the callback
583 * @param callback the callback function to be invoked when an incident report with all sections
584 * or sections matching the given id is being taken
585 */
586 public void registerSection(int id, @NonNull String name,
587 @NonNull @CallbackExecutor Executor executor, @NonNull DumpCallback callback) {
588 Objects.requireNonNull(executor, "executor cannot be null");
589 Objects.requireNonNull(callback, "callback cannot be null");
590 try {
591 if (callback.mExecutor != null) {
592 throw new RuntimeException("Do not reuse DumpCallback objects when calling"
593 + " registerSection");
594 }
595 callback.mExecutor = executor;
596 callback.mId = id;
597 final IIncidentManager service = getIIncidentManagerLocked();
598 if (service == null) {
599 Slog.e(TAG, "registerSection can't find incident binder service");
600 return;
601 }
602 service.registerSection(id, name, callback.mBinder);
603 } catch (RemoteException ex) {
604 Slog.e(TAG, "registerSection failed", ex);
605 }
606 }
607
608 /**
609 * Unregister an extended section dump function. The section must be previously registered with
610 * {@link #registerSection(int, String, DumpCallback)} by the same calling uid.
611 */
612 public void unregisterSection(int id) {
613 try {
614 final IIncidentManager service = getIIncidentManagerLocked();
615 if (service == null) {
616 Slog.e(TAG, "unregisterSection can't find incident binder service");
617 return;
618 }
619 service.unregisterSection(id);
620 } catch (RemoteException ex) {
621 Slog.e(TAG, "unregisterSection failed", ex);
622 }
623 }
624
625 /**
626 * Get the incident reports that are available for upload for the supplied
627 * broadcast recevier.
628 *
629 * @param receiverClass Class name of broadcast receiver in this package that
630 * was registered to retrieve reports.
631 *
632 * @return A list of {@link Uri Uris} that are awaiting upload.
633 */
634 @RequiresPermission(allOf = {
635 android.Manifest.permission.DUMP,
636 android.Manifest.permission.PACKAGE_USAGE_STATS
637 })
638 public @NonNull List<Uri> getIncidentReportList(String receiverClass) {
639 List<String> strings;
640 try {
641 strings = getCompanionServiceLocked().getIncidentReportList(
642 mContext.getPackageName(), receiverClass);
643 } catch (RemoteException ex) {
644 throw new RuntimeException("System server or incidentd going down", ex);
645 }
646 final int size = strings.size();
647 ArrayList<Uri> result = new ArrayList(size);
648 for (int i = 0; i < size; i++) {
649 result.add(Uri.parse(strings.get(i)));
650 }
651 return result;
652 }
653
654 /**
655 * Get the incident report with the given URI id.
656 *
657 * @param uri Identifier of the incident report.
658 *
659 * @return an IncidentReport object, or null if the incident report has been
660 * expired from disk.
661 */
662 @RequiresPermission(allOf = {
663 android.Manifest.permission.DUMP,
664 android.Manifest.permission.PACKAGE_USAGE_STATS
665 })
666 public @Nullable IncidentReport getIncidentReport(Uri uri) {
667 final String id = uri.getQueryParameter(URI_PARAM_REPORT_ID);
668 if (id == null) {
669 // If there's no report id, it's a bug report, so we can't return the incident
670 // report.
671 return null;
672 }
673
674 final String pkg = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
675 if (pkg == null) {
676 throw new RuntimeException("Invalid URI: No "
677 + URI_PARAM_CALLING_PACKAGE + " parameter. " + uri);
678 }
679
680 final String cls = uri.getQueryParameter(URI_PARAM_RECEIVER_CLASS);
681 if (cls == null) {
682 throw new RuntimeException("Invalid URI: No "
683 + URI_PARAM_RECEIVER_CLASS + " parameter. " + uri);
684 }
685
686 try {
687 return getCompanionServiceLocked().getIncidentReport(pkg, cls, id);
688 } catch (RemoteException ex) {
689 throw new RuntimeException("System server or incidentd going down", ex);
690 }
691 }
692
693 /**
694 * Delete the incident report with the given URI id.
695 *
696 * @param uri Identifier of the incident report. Pass null to delete all
697 * incident reports owned by this application.
698 */
699 @RequiresPermission(allOf = {
700 android.Manifest.permission.DUMP,
701 android.Manifest.permission.PACKAGE_USAGE_STATS
702 })
703 public void deleteIncidentReports(Uri uri) {
704 if (uri == null) {
705 try {
706 getCompanionServiceLocked().deleteAllIncidentReports(mContext.getPackageName());
707 } catch (RemoteException ex) {
708 throw new RuntimeException("System server or incidentd going down", ex);
709 }
710 } else {
711 final String pkg = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE);
712 if (pkg == null) {
713 throw new RuntimeException("Invalid URI: No "
714 + URI_PARAM_CALLING_PACKAGE + " parameter. " + uri);
715 }
716
717 final String cls = uri.getQueryParameter(URI_PARAM_RECEIVER_CLASS);
718 if (cls == null) {
719 throw new RuntimeException("Invalid URI: No "
720 + URI_PARAM_RECEIVER_CLASS + " parameter. " + uri);
721 }
722
723 final String id = uri.getQueryParameter(URI_PARAM_REPORT_ID);
724 if (id == null) {
725 throw new RuntimeException("Invalid URI: No "
726 + URI_PARAM_REPORT_ID + " parameter. " + uri);
727 }
728
729 try {
730 getCompanionServiceLocked().deleteIncidentReports(pkg, cls, id);
731 } catch (RemoteException ex) {
732 throw new RuntimeException("System server or incidentd going down", ex);
733 }
734 }
735 }
736
737 private void reportIncidentInternal(IncidentReportArgs args) {
738 try {
739 final IIncidentManager service = getIIncidentManagerLocked();
740 if (service == null) {
741 Slog.e(TAG, "reportIncident can't find incident binder service");
742 return;
743 }
744 service.reportIncident(args);
745 } catch (RemoteException ex) {
746 Slog.e(TAG, "reportIncident failed", ex);
747 }
748 }
749
750 private IIncidentManager getIIncidentManagerLocked() throws RemoteException {
751 if (mIncidentService != null) {
752 return mIncidentService;
753 }
754
755 synchronized (mLock) {
756 if (mIncidentService != null) {
757 return mIncidentService;
758 }
759 mIncidentService = IIncidentManager.Stub.asInterface(
760 ServiceManager.getService(Context.INCIDENT_SERVICE));
761 if (mIncidentService != null) {
762 mIncidentService.asBinder().linkToDeath(() -> {
763 synchronized (mLock) {
764 mIncidentService = null;
765 }
766 }, 0);
767 }
768 return mIncidentService;
769 }
770 }
771
772 private IIncidentCompanion getCompanionServiceLocked() throws RemoteException {
773 if (mCompanionService != null) {
774 return mCompanionService;
775 }
776
777 synchronized (this) {
778 if (mCompanionService != null) {
779 return mCompanionService;
780 }
781 mCompanionService = IIncidentCompanion.Stub.asInterface(
782 ServiceManager.getService(Context.INCIDENT_COMPANION_SERVICE));
783 if (mCompanionService != null) {
784 mCompanionService.asBinder().linkToDeath(() -> {
785 synchronized (mLock) {
786 mCompanionService = null;
787 }
788 }, 0);
789 }
790 return mCompanionService;
791 }
792 }
793}
794