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