Home | History | Annotate | Download | only in os
      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