From 5a15b55107651968312f39a830ddb26909b9d362 Mon Sep 17 00:00:00 2001 From: Joe Onorato Date: Tue, 18 Dec 2018 10:40:04 -0800 Subject: [PATCH 1/2] Added a new "incidentReportApprover" permission protection flag. This gives the ONE app installed on the system the permission to authorize the sharing of incident and bug reports with an app. Currently that app is PermissionController. Package Manager enforces that there is only one installed, and refused to boot if that isn't the case. Bug: 123543706 Test: bit GooglePermissionControllerTest:* Change-Id: I76a7fad0ea36359bbb0ff09669df46202d059dab --- api/system-current.txt | 3 +++ api/test-current.txt | 3 +++ .../android/app/ApplicationPackageManager.java | 9 +++++++++ core/java/android/content/pm/IPackageManager.aidl | 2 ++ core/java/android/content/pm/PackageManager.java | 13 +++++++++++++ .../android/content/pm/PackageManagerInternal.java | 2 ++ core/java/android/content/pm/PermissionInfo.java | 14 ++++++++++++++ core/res/AndroidManifest.xml | 10 ++++++++++ core/res/res/values/attrs_manifest.xml | 3 +++ core/res/res/values/config.xml | 7 +++++++ core/res/res/values/symbols.xml | 2 ++ data/etc/privapp-permissions-platform.xml | 1 + .../android/server/pm/PackageManagerService.java | 8 ++++++++ .../server/pm/permission/BasePermission.java | 3 +++ .../pm/permission/PermissionManagerService.java | 8 ++++++++ .../backup/testutils/IPackageManagerStub.java | 4 ++++ 16 files changed, 92 insertions(+) diff --git a/api/system-current.txt b/api/system-current.txt index da3b7f4239ed4..7214eb7112ab3 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -18,6 +18,7 @@ package android { field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE"; field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"; field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER"; + field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String BACKUP = "android.permission.BACKUP"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; @@ -1489,6 +1490,7 @@ package android.content.pm { method @NonNull public android.content.pm.dex.ArtManager getArtManager(); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract String getDefaultBrowserPackageNameAsUser(int); method @Nullable @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public CharSequence getHarmfulAppWarning(@NonNull String); + method public String getIncidentReportApproverPackageName(); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List getInstalledPackagesAsUser(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract android.graphics.drawable.Drawable getInstantAppIcon(String); method public abstract android.content.ComponentName getInstantAppInstallerComponent(); @@ -1610,6 +1612,7 @@ package android.content.pm { field public static final int FLAG_REMOVED = 2; // 0x2 field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000 field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000 + field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000 field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000 field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000 field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000 diff --git a/api/test-current.txt b/api/test-current.txt index 9c27535664fe4..2d654846aef02 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4,6 +4,7 @@ package android { public static final class Manifest.permission { field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; + field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; @@ -413,6 +414,7 @@ package android.content.pm { public abstract class PackageManager { method public abstract boolean arePermissionsIndividuallyControlled(); method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract String getDefaultBrowserPackageNameAsUser(int); + method public String getIncidentReportApproverPackageName(); method public abstract int getInstallReason(String, @NonNull android.os.UserHandle); method public abstract java.util.List getInstalledApplicationsAsUser(int, int); method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract java.util.List getInstalledPackagesAsUser(int, int); @@ -434,6 +436,7 @@ package android.content.pm { public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { field public static final int PROTECTION_FLAG_CONFIGURATOR = 524288; // 0x80000 field public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000 + field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000 field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000 field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000 field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000 diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 4491b95f968a9..a937422733066 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -3025,6 +3025,15 @@ public class ApplicationPackageManager extends PackageManager { } } + @Override + public String getIncidentReportApproverPackageName() { + try { + return mPM.getIncidentReportApproverPackageName(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + @Override public boolean isPackageStateProtected(String packageName, int userId) { try { diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 7bd8c4e244c71..a6a6f013839ae 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -688,6 +688,8 @@ interface IPackageManager { String getContentCaptureServicePackageName(); + String getIncidentReportApproverPackageName(); + boolean isPackageStateProtected(String packageName, int userId); void sendDeviceCustomizationReadyBroadcast(); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 0aa33c89f2de3..3a675c901a9f3 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -6743,6 +6743,19 @@ public abstract class PackageManager { "getContentCaptureServicePackageName not implemented in subclass"); } + /** + * @return the incident report approver app package name, or null if it's not defined + * by the OEM. + * + * @hide + */ + @SystemApi + @TestApi + public String getIncidentReportApproverPackageName() { + throw new UnsupportedOperationException( + "getIncidentReportApproverPackageName not implemented in subclass"); + } + /** * @return whether a given package's state is protected, e.g. package cannot be disabled, * suspended, hidden or force stopped. diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index c9a4c82703906..738730ec3f516 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -56,6 +56,7 @@ public abstract class PackageManagerInternal { public static final int PACKAGE_WELLBEING = 7; public static final int PACKAGE_DOCUMENTER = 8; public static final int PACKAGE_CONFIGURATOR = 9; + public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10; @IntDef(value = { PACKAGE_SYSTEM, PACKAGE_SETUP_WIZARD, @@ -67,6 +68,7 @@ public abstract class PackageManagerInternal { PACKAGE_WELLBEING, PACKAGE_DOCUMENTER, PACKAGE_CONFIGURATOR, + PACKAGE_INCIDENT_REPORT_APPROVER, }) @Retention(RetentionPolicy.SOURCE) public @interface KnownPackage {} diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 5d2cf0a7d101e..e7769849ccc3f 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -213,6 +213,16 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { @TestApi public static final int PROTECTION_FLAG_CONFIGURATOR = 0x80000; + /** + * Additional flag for {${link #protectionLevel}, corresponding + * to the incident_report_approver value of + * {@link android.R.attr#protectionLevel}. + * + * @hide + */ + @SystemApi + @TestApi + public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 0x100000; /** @hide */ @IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = { @@ -233,6 +243,7 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { PROTECTION_FLAG_WELLBEING, PROTECTION_FLAG_DOCUMENTER, PROTECTION_FLAG_CONFIGURATOR, + PROTECTION_FLAG_INCIDENT_REPORT_APPROVER, }) @Retention(RetentionPolicy.SOURCE) public @interface ProtectionFlags {} @@ -431,6 +442,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { if ((level & PROTECTION_FLAG_CONFIGURATOR) != 0) { protLevel += "|configurator"; } + if ((level & PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER) != 0) { + protLevel += "|incidentReportApprover"; + } return protLevel; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7184c7a43da75..14143d857a580 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -115,6 +115,7 @@ + @@ -2798,6 +2799,15 @@ + + + diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index de6468dbb72a4..0abe456c82f92 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -277,6 +277,9 @@ + + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1db8135d31c7c..67b06525a7e40 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3493,6 +3493,13 @@ --> + + com.android.permissioncontroller + + + diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 1684138455e51..3331f3a88fc24 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -141,6 +141,7 @@ applications that come with the platform + diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 265d07a828873..d2547d92e8b92 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1350,6 +1350,7 @@ public class PackageManagerService extends IPackageManager.Stub final @Nullable String mDocumenterPackage; final @Nullable String mConfiguratorPackage; final @Nullable String mAppPredictionServicePackage; + final @Nullable String mIncidentReportApproverPackage; final @NonNull String mServicesSystemSharedLibraryPackageName; final @NonNull String mSharedSystemSharedLibraryPackageName; @@ -2879,6 +2880,7 @@ public class PackageManagerService extends IPackageManager.Stub mConfiguratorPackage = mContext.getString(R.string.config_deviceConfiguratorPackageName); mAppPredictionServicePackage = getAppPredictionServicePackageName(); + mIncidentReportApproverPackage = getIncidentReportApproverPackageName(); // Now that we know all of the shared libraries, update all clients to have // the correct library paths. @@ -20112,6 +20114,10 @@ public class PackageManagerService extends IPackageManager.Stub return contentCaptureServiceName.substring(0, separatorIndex); } + public String getIncidentReportApproverPackageName() { + return mContext.getString(R.string.config_incidentReportApproverPackage); + } + @Override public void setApplicationEnabledSetting(String appPackageName, int newState, int flags, int userId, String callingPackage) { @@ -23275,6 +23281,8 @@ public class PackageManagerService extends IPackageManager.Stub return mDocumenterPackage; case PackageManagerInternal.PACKAGE_CONFIGURATOR: return mConfiguratorPackage; + case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER: + return mIncidentReportApproverPackage; } return null; } diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java index 17f83479a3b13..848cee06d40b7 100644 --- a/services/core/java/com/android/server/pm/permission/BasePermission.java +++ b/services/core/java/com/android/server/pm/permission/BasePermission.java @@ -251,6 +251,9 @@ public final class BasePermission { return (protectionLevel & PermissionInfo.PROTECTION_FLAG_CONFIGURATOR) != 0; } + public boolean isIncidentReportApprover() { + return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER) != 0; + } public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) { if (!origPackageName.equals(sourcePackageName)) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 3c89d78018521..22780e676f721 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1716,6 +1716,14 @@ public class PermissionManagerService { // this app is the documenter, then it gets the permission. allowed = true; } + if (!allowed && bp.isIncidentReportApprover() + && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName( + PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER, + UserHandle.USER_SYSTEM))) { + // If this permission is to be granted to the incident report approver and + // this app is the incident report approver, then it gets the permission. + allowed = true; + } } return allowed; } diff --git a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java index 5b02266837302..a01d589f25b3d 100644 --- a/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java +++ b/services/tests/servicestests/src/com/android/server/backup/testutils/IPackageManagerStub.java @@ -1151,6 +1151,10 @@ public class IPackageManagerStub implements IPackageManager { return null; } + public String getIncidentReportApproverPackageName() throws RemoteException { + return null; + } + @Override public boolean isPackageStateProtected(String packageName, int userId) throws RemoteException { return false; From e21ab7eab46d1c99e0e3e3506edaca096f796e2d Mon Sep 17 00:00:00 2001 From: Joe Onorato Date: Tue, 18 Dec 2018 15:00:25 -0800 Subject: [PATCH 2/2] Add IncidentCompanionService It is a helper in the system process that helps dumpstate and incidentd get user confirmation to share reports Bug: 123543706 Test: bit GooglePermissionControllerTest:* Change-Id: Ia3fe4bd5257044ed89fe56ce683876fa03ba6c36 --- Android.bp | 2 + api/system-current.txt | 21 + api/test-current.txt | 20 + core/java/android/content/Context.java | 8 + core/java/android/content/Intent.java | 12 + core/java/android/os/IncidentManager.java | 317 +++++++++- core/res/AndroidManifest.xml | 5 + data/etc/platform.xml | 1 + data/etc/privapp-permissions-platform.xml | 2 + .../incident/IncidentCompanionService.java | 545 ++++++++++++++++++ .../android/server/incident/RequestQueue.java | 155 +++++ .../java/com/android/server/SystemServer.java | 6 + 12 files changed, 1078 insertions(+), 16 deletions(-) create mode 100644 services/core/java/com/android/server/incident/IncidentCompanionService.java create mode 100644 services/core/java/com/android/server/incident/RequestQueue.java diff --git a/Android.bp b/Android.bp index 7ce8b31e42c2e..6c5473a6864ae 100644 --- a/Android.bp +++ b/Android.bp @@ -656,6 +656,7 @@ java_defaults { ":vold_aidl", ":installd_aidl", ":dumpstate_aidl", + ":incidentcompanion_aidl", "lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl", "lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl", @@ -706,6 +707,7 @@ java_defaults { "system/update_engine/binder_bindings", "frameworks/native/aidl/binder", "frameworks/native/cmds/dumpstate/binder", + "frameworks/native/libs/incidentcompanion/binder", "frameworks/av/camera/aidl", "frameworks/av/media/libaudioclient/aidl", "frameworks/native/aidl/gui", diff --git a/api/system-current.txt b/api/system-current.txt index 7214eb7112ab3..ca1dee39b4978 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1307,6 +1307,7 @@ package android.content { field public static final String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS"; field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_SPECIAL_APP_ACCESSES = "android.intent.action.MANAGE_SPECIAL_APP_ACCESSES"; field public static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION"; + field public static final String ACTION_PENDING_INCIDENT_REPORTS_CHANGED = "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED"; field public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; field public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; field public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; @@ -5333,7 +5334,27 @@ package android.os { } public class IncidentManager { + method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public void approveReport(android.net.Uri); + method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void cancelAuthorization(android.os.IncidentManager.AuthListener); + method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public void denyReport(android.net.Uri); + method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public java.util.List getPendingReports(); method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void reportIncident(android.os.IncidentReportArgs); + method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener); + field public static final int FLAG_CONFIRMATION_DIALOG = 1; // 0x1 + } + + public static class IncidentManager.AuthListener { + ctor public IncidentManager.AuthListener(); + method public void onReportApproved(); + method public void onReportDenied(); + } + + public static class IncidentManager.PendingReport { + ctor public IncidentManager.PendingReport(@NonNull android.net.Uri); + method public int getFlags(); + method @NonNull public String getRequestingPackage(); + method public long getTimestamp(); + method @NonNull public android.net.Uri getUri(); } public final class IncidentReportArgs implements android.os.Parcelable { diff --git a/api/test-current.txt b/api/test-current.txt index 2d654846aef02..4ca3f568243cc 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1282,7 +1282,27 @@ package android.os { } public class IncidentManager { + method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public void approveReport(android.net.Uri); + method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void cancelAuthorization(android.os.IncidentManager.AuthListener); + method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public void denyReport(android.net.Uri); + method @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) public java.util.List getPendingReports(); method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void reportIncident(android.os.IncidentReportArgs); + method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener); + field public static final int FLAG_CONFIRMATION_DIALOG = 1; // 0x1 + } + + public static class IncidentManager.AuthListener { + ctor public IncidentManager.AuthListener(); + method public void onReportApproved(); + method public void onReportDenied(); + } + + public static class IncidentManager.PendingReport { + ctor public IncidentManager.PendingReport(@NonNull android.net.Uri); + method public int getFlags(); + method @NonNull public String getRequestingPackage(); + method public long getTimestamp(); + method @NonNull public android.net.Uri getUri(); } public final class IncidentReportArgs implements android.os.Parcelable { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 87f9e464cdc20..d9d0ee98e5ac4 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3180,6 +3180,7 @@ public abstract class Context { //@hide: CONTEXTHUB_SERVICE, SYSTEM_HEALTH_SERVICE, //@hide: INCIDENT_SERVICE, + //@hide: INCIDENT_COMPANION_SERVICE, //@hide: STATS_COMPANION_SERVICE, COMPANION_DEVICE_SERVICE, CROSS_PROFILE_APPS_SERVICE, @@ -4465,6 +4466,13 @@ public abstract class Context { */ public static final String INCIDENT_SERVICE = "incident"; + /** + * Service to assist incidentd and dumpstated in reporting status to the user + * and in confirming authorization to take an incident report or bugreport + * @hide + */ + public static final String INCIDENT_COMPANION_SERVICE = "incidentcompanion"; + /** * Service to assist statsd in obtaining general stats. * @hide diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 22f73dbd4644b..8d14091478c31 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1474,6 +1474,18 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR"; + /** + * An incident or bug report has been taken, and a system app has requested it to be shared, + * so trigger the confirmation screen. + * + * This will be sent directly to the registered receiver with the + * android.permission.APPROVE_INCIDENT_REPORTS permission. + * @hide + */ + @SystemApi + public static final String ACTION_PENDING_INCIDENT_REPORTS_CHANGED = + "android.intent.action.PENDING_INCIDENT_REPORTS_CHANGED"; + /** * Activity Action: Show power usage information to the user. *

Input: Nothing. diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java index 0e6652d471a54..88a578a5b6de8 100644 --- a/core/java/android/os/IncidentManager.java +++ b/core/java/android/os/IncidentManager.java @@ -16,13 +16,18 @@ package android.os; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; +import android.net.Uri; import android.util.Slog; +import java.util.ArrayList; +import java.util.List; + /** * Class to take an incident report. * @@ -34,9 +39,200 @@ import android.util.Slog; public class IncidentManager { private static final String TAG = "IncidentManager"; + /** + * Authority for pending report id urls. + * + * @hide + */ + public static final String URI_SCHEME = "content"; + + /** + * Authority for pending report id urls. + * + * @hide + */ + public static final String URI_AUTHORITY = "android.os.IncidentManager"; + + /** + * Authority for pending report id urls. + * + * @hide + */ + public static final String URI_PATH = "/pending"; + + /** + * Query parameter for the uris for the pending report id. + * + * @hide + */ + public static final String URI_PARAM_ID = "id"; + + /** + * Query parameter for the uris for the pending report id. + * + * @hide + */ + public static final String URI_PARAM_CALLING_PACKAGE = "pkg"; + + /** + * Query parameter for the uris for the pending report id, in wall clock + * ({@link System.currentTimeMillis()}) timebase. + * + * @hide + */ + public static final String URI_PARAM_TIMESTAMP = "t"; + + /** + * Query parameter for the uris for the pending report id. + * + * @hide + */ + public static final String URI_PARAM_FLAGS = "flags"; + + /** + * Do the confirmation with a dialog instead of the default, which is a notification. + * It is possible for the dialog to be downgraded to a notification in some cases. + */ + public static final int FLAG_CONFIRMATION_DIALOG = 0x1; + private final Context mContext; - private IIncidentManager mService; + private Object mLock = new Object(); + private IIncidentManager mIncidentService; + private IIncidentCompanion mCompanionService; + + /** + * Record for a report that has been taken and is pending user authorization + * to share it. + * @hide + */ + @SystemApi + @TestApi + public static class PendingReport { + /** + * Encoded data. + */ + private final Uri mUri; + + /** + * URI_PARAM_FLAGS from the uri + */ + private final int mFlags; + + /** + * URI_PARAM_CALLING_PACKAGE from the uri + */ + private final String mRequestingPackage; + + /** + * URI_PARAM_TIMESTAMP from the uri + */ + private final long mTimestamp; + + /** + * Constructor. + */ + public PendingReport(@NonNull Uri uri) { + int flags = 0; + try { + flags = Integer.parseInt(uri.getQueryParameter(URI_PARAM_FLAGS)); + } catch (NumberFormatException ex) { + throw new RuntimeException("Invalid URI: No " + URI_PARAM_FLAGS + + " parameter. " + uri); + } + mFlags = flags; + + String requestingPackage = uri.getQueryParameter(URI_PARAM_CALLING_PACKAGE); + if (requestingPackage == null) { + throw new RuntimeException("Invalid URI: No " + URI_PARAM_CALLING_PACKAGE + + " parameter. " + uri); + } + mRequestingPackage = requestingPackage; + + long timestamp = -1; + try { + timestamp = Long.parseLong(uri.getQueryParameter(URI_PARAM_TIMESTAMP)); + } catch (NumberFormatException ex) { + throw new RuntimeException("Invalid URI: No " + URI_PARAM_TIMESTAMP + + " parameter. " + uri); + } + mTimestamp = timestamp; + + mUri = uri; + } + + /** + * Get the package with which this report will be shared. + */ + public @NonNull String getRequestingPackage() { + return mRequestingPackage; + } + + /** + * Get the flags requested for this pending report. + * + * @see #FLAG_CONFIRMATION_DIALOG + */ + public int getFlags() { + return mFlags; + } + + /** + * Get the time this pending report was posted. + */ + public long getTimestamp() { + return mTimestamp; + } + + /** + * Get the URI associated with this PendingReport. It can be used to + * re-retrieve it from {@link IncidentManager} or set as the data field of + * an Intent. + */ + public @NonNull Uri getUri() { + return mUri; + } + + /** + * String representation of this PendingReport. + */ + @Override + public @NonNull String toString() { + return "PendingReport(" + getUri().toString() + ")"; + } + } + + /** + * Listener for the status of an incident report being authroized or denied. + * + * @see #requestAuthorization + * @see #cancelAuthorization + */ + public static class AuthListener { + IIncidentAuthListener.Stub mBinder = new IIncidentAuthListener.Stub() { + @Override + public void onReportApproved() { + AuthListener.this.onReportApproved(); + } + + @Override + public void onReportDenied() { + AuthListener.this.onReportDenied(); + } + }; + + /** + * Called when a report is approved. + */ + public void onReportApproved() { + } + + /** + * Called when a report is denied. + */ + public void onReportDenied() { + } + } /** * @hide @@ -56,12 +252,76 @@ public class IncidentManager { reportIncidentInternal(args); } - private class IncidentdDeathRecipient implements IBinder.DeathRecipient { - @Override - public void binderDied() { - synchronized (this) { - mService = null; - } + /** + * Request authorization of an incident report. + */ + @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL) + public void requestAuthorization(int callingUid, String callingPackage, int flags, + AuthListener listener) { + try { + getCompanionServiceLocked().authorizeReport(callingUid, callingPackage, flags, + listener.mBinder); + } catch (RemoteException ex) { + // System process going down + throw new RuntimeException(ex); + } + } + + /** + * Cancel a previous request for incident report authorization. + */ + @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL) + public void cancelAuthorization(AuthListener listener) { + try { + getCompanionServiceLocked().cancelAuthorization(listener.mBinder); + } catch (RemoteException ex) { + // System process going down + throw new RuntimeException(ex); + } + } + + /** + * Get incident (and bug) reports that are pending approval to share. + */ + @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) + public List getPendingReports() { + List strings; + try { + strings = getCompanionServiceLocked().getPendingReports(); + } catch (RemoteException ex) { + throw new RuntimeException(ex); + } + final int size = strings.size(); + ArrayList result = new ArrayList(size); + for (int i = 0; i < size; i++) { + result.add(new PendingReport(Uri.parse(strings.get(i)))); + } + return result; + } + + /** + * Allow this report to be shared with the given app. + */ + @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) + public void approveReport(Uri uri) { + try { + getCompanionServiceLocked().approveReport(uri.toString()); + } catch (RemoteException ex) { + // System process going down + throw new RuntimeException(ex); + } + } + + /** + * Do not allow this report to be shared with the given app. + */ + @RequiresPermission(android.Manifest.permission.APPROVE_INCIDENT_REPORTS) + public void denyReport(Uri uri) { + try { + getCompanionServiceLocked().denyReport(uri.toString()); + } catch (RemoteException ex) { + // System process going down + throw new RuntimeException(ex); } } @@ -79,22 +339,47 @@ public class IncidentManager { } private IIncidentManager getIIncidentManagerLocked() throws RemoteException { - if (mService != null) { - return mService; + if (mIncidentService != null) { + return mIncidentService; } - synchronized (this) { - if (mService != null) { - return mService; + synchronized (mLock) { + if (mIncidentService != null) { + return mIncidentService; } - mService = IIncidentManager.Stub.asInterface( + mIncidentService = IIncidentManager.Stub.asInterface( ServiceManager.getService(Context.INCIDENT_SERVICE)); - if (mService != null) { - mService.asBinder().linkToDeath(new IncidentdDeathRecipient(), 0); + if (mIncidentService != null) { + mIncidentService.asBinder().linkToDeath(() -> { + synchronized (mLock) { + mIncidentService = null; + } + }, 0); } - return mService; + return mIncidentService; } } + private IIncidentCompanion getCompanionServiceLocked() throws RemoteException { + if (mCompanionService != null) { + return mCompanionService; + } + + synchronized (this) { + if (mCompanionService != null) { + return mCompanionService; + } + mCompanionService = IIncidentCompanion.Stub.asInterface( + ServiceManager.getService(Context.INCIDENT_COMPANION_SERVICE)); + if (mCompanionService != null) { + mCompanionService.asBinder().linkToDeath(() -> { + synchronized (mLock) { + mCompanionService = null; + } + }, 0); + } + return mCompanionService; + } + } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 14143d857a580..1a402c064b0e4 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2808,6 +2808,11 @@ + + + diff --git a/data/etc/platform.xml b/data/etc/platform.xml index fb43e41010b44..44d71e269a957 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -169,6 +169,7 @@ + diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 3331f3a88fc24..a47ab875804ff 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -141,6 +141,7 @@ applications that come with the platform + @@ -328,6 +329,7 @@ applications that come with the platform + diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java new file mode 100644 index 0000000000000..3ebba0074a1c9 --- /dev/null +++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java @@ -0,0 +1,545 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.incident; + +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.net.Uri; +import android.os.Binder; +import android.os.Handler; +import android.os.IIncidentAuthListener; +import android.os.IIncidentCompanion; +import android.os.IncidentManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import com.android.internal.util.DumpUtils; +import com.android.server.SystemService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +// TODO: User changes should deny everything that's pending. + +/** + * Helper service for incidentd and dumpstated to provide user feedback + * and authorization for bug and inicdent reports to be taken. + */ +public class IncidentCompanionService extends SystemService { + static final String TAG = "IncidentCompanionService"; + + private final Handler mHandler = new Handler(); + private final RequestQueue mRequestQueue = new RequestQueue(mHandler); + private final PackageManager mPackageManager; + private final AppOpsManager mAppOpsManager; + + // + // All fields below must be protected by mLock + // + private final Object mLock = new Object(); + private final ArrayList mPending = new ArrayList(); + + /** + * The next ID we'll use when we make a PendingReportRec. + */ + private int mNextPendingId = 1; + + /** + * One for each authorization that's pending. + */ + private final class PendingReportRec { + public int id; + public String callingPackage; + public int flags; + public IIncidentAuthListener listener; + public long addedRealtime; + public long addedWalltime; + + /** + * Construct a PendingReportRec, with an auto-incremented id. + */ + PendingReportRec(String callingPackage, int flags, IIncidentAuthListener listener) { + this.id = mNextPendingId++; + this.callingPackage = callingPackage; + this.flags = flags; + this.listener = listener; + this.addedRealtime = SystemClock.elapsedRealtime(); + this.addedWalltime = System.currentTimeMillis(); + } + + /** + * Get the Uri that contains the flattened data. + */ + Uri getUri() { + return (new Uri.Builder()) + .scheme(IncidentManager.URI_SCHEME) + .authority(IncidentManager.URI_AUTHORITY) + .path(IncidentManager.URI_PATH) + .appendQueryParameter(IncidentManager.URI_PARAM_ID, Integer.toString(id)) + .appendQueryParameter(IncidentManager.URI_PARAM_CALLING_PACKAGE, callingPackage) + .appendQueryParameter(IncidentManager.URI_PARAM_FLAGS, Integer.toString(flags)) + .appendQueryParameter(IncidentManager.URI_PARAM_TIMESTAMP, + Long.toString(addedWalltime)) + .build(); + } + } + + /** + * Implementation of the IIncidentCompanion binder interface. + */ + private final class BinderService extends IIncidentCompanion.Stub { + /** + * ONEWAY binder call to initiate authorizing the report. The actual logic is posted + * to mRequestQueue, and may happen later. The security checks need to happen here. + */ + @Override + public void authorizeReport(int callingUid, final String callingPackage, final int flags, + final IIncidentAuthListener listener) { + enforceRequestAuthorizationPermission(); + + final long ident = Binder.clearCallingIdentity(); + try { + // Starting the system server is complicated, and rather than try to + // have a complicated lifecycle that we share with dumpstated and incidentd, + // we will accept the request, and then display it whenever it becomes possible to. + mRequestQueue.enqueue(listener.asBinder(), true, () -> { + authorizeReportImpl(callingUid, callingPackage, flags, listener); + }); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * ONEWAY binder call to cancel the inbound authorization request. + *

+ * This is a oneway call, and so is authorizeReport, so the + * caller's ordering is preserved. The other calls on this object are synchronous, so + * their ordering is not guaranteed with respect to these calls. So the implementation + * sends out extra broadcasts to allow for eventual consistency. + */ + public void cancelAuthorization(final IIncidentAuthListener listener) { + enforceRequestAuthorizationPermission(); + + // Caller can cancel if they don't want it anymore, and mRequestQueue elides + // authorize/cancel pairs. + final long ident = Binder.clearCallingIdentity(); + try { + mRequestQueue.enqueue(listener.asBinder(), false, () -> { + cancelReportImpl(listener); + }); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * SYNCHRONOUS binder call to get the list of reports that are pending confirmation + * by the user. + */ + @Override + public List getPendingReports() { + enforceAuthorizePermission(); + + synchronized (mLock) { + final int size = mPending.size(); + final ArrayList result = new ArrayList(size); + for (int i = 0; i < size; i++) { + result.add(mPending.get(i).getUri().toString()); + } + return result; + } + } + + /** + * ONEWAY binder call to mark a report as approved. + */ + @Override + public void approveReport(String uri) { + enforceAuthorizePermission(); + + final long ident = Binder.clearCallingIdentity(); + try { + final PendingReportRec rec; + synchronized (mLock) { + rec = findAndRemovePendingReportRecLocked(uri); + if (rec == null) { + Log.e(TAG, "confirmApproved: Couldn't find record for uri: " + uri); + return; + } + } + + // Re-do the broadcast, so whoever is listening knows the list changed, + // in case another one was added in the meantime. + sendBroadcast(); + + Log.i(TAG, "Approved report: " + uri); + try { + rec.listener.onReportApproved(); + } catch (RemoteException ex) { + Log.w(TAG, "Failed calling back for approval for: " + uri, ex); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * ONEWAY binder call to mark a report as NOT approved. + */ + @Override + public void denyReport(String uri) { + enforceAuthorizePermission(); + + final long ident = Binder.clearCallingIdentity(); + try { + final PendingReportRec rec; + synchronized (mLock) { + rec = findAndRemovePendingReportRecLocked(uri); + if (rec == null) { + Log.e(TAG, "confirmDenied: Couldn't find record for uri: " + uri); + return; + } + } + + // Re-do the broadcast, so whoever is listening knows the list changed, + // in case another one was added in the meantime. + sendBroadcast(); + + Log.i(TAG, "Denied report: " + uri); + try { + rec.listener.onReportDenied(); + } catch (RemoteException ex) { + Log.w(TAG, "Failed calling back for denial for: " + uri, ex); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * Implementation of adb shell dumpsys debugreportcompanion. + */ + @Override + protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { + if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) { + return; + } + if (args.length == 0) { + // Standard text dumpsys + final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + synchronized (mLock) { + final int size = mPending.size(); + writer.println("mPending: (" + size + ")"); + for (int i = 0; i < size; i++) { + final PendingReportRec entry = mPending.get(i); + writer.println(String.format(" %11d %s: %s", entry.addedRealtime, + df.format(new Date(entry.addedWalltime)), + entry.getUri().toString())); + } + } + } + } + + private void enforceRequestAuthorizationPermission() { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL, null); + } + + private void enforceAuthorizePermission() { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.APPROVE_INCIDENT_REPORTS, null); + } + + } + + /** + * Construct new IncidentCompanionService with the context. + */ + public IncidentCompanionService(Context context) { + super(context); + mPackageManager = context.getPackageManager(); + mAppOpsManager = context.getSystemService(AppOpsManager.class); + } + + /** + * Initialize the service. It is still not safe to do UI until + * onBootPhase(SystemService.PHASE_BOOT_COMPLETED). + */ + @Override + public void onStart() { + publishBinderService(Context.INCIDENT_COMPANION_SERVICE, new BinderService()); + } + + /** + * Handle the boot process... Starts everything running once the system is + * up enough for us to do UI. + */ + @Override + public void onBootPhase(int phase) { + super.onBootPhase(phase); + switch (phase) { + case SystemService.PHASE_BOOT_COMPLETED: + // Release the enqueued work. + mRequestQueue.start(); + break; + } + } + + /** + * Start the confirmation process. + */ + private void authorizeReportImpl(int callingUid, final String callingPackage, int flags, + final IIncidentAuthListener listener) { + // Enforce that the calling package pertains to the callingUid. + if (!isPackageInUid(callingUid, callingPackage)) { + Log.w(TAG, "Calling uid " + callingUid + " doesn't match package " + + callingPackage); + denyReportBeforeAddingRec(listener, callingPackage); + return; + } + + // Find the primary user of this device. + final int primaryUser = getAndValidateUser(); + if (primaryUser == UserHandle.USER_NULL) { + denyReportBeforeAddingRec(listener, callingPackage); + return; + } + + // Find the approver app (hint: it's PermissionController). + final ComponentName receiver = getApproverComponent(primaryUser); + if (receiver == null) { + // We couldn't find an approver... so deny the request here and now, before we + // do anything else. + denyReportBeforeAddingRec(listener, callingPackage); + return; + } + + // Save the record for when the PermissionController comes back to authorize it. + PendingReportRec rec = null; + synchronized (mLock) { + rec = new PendingReportRec(callingPackage, flags, listener); + mPending.add(rec); + } + + try { + listener.asBinder().linkToDeath(() -> { + Log.i(TAG, "Got death notification listener=" + listener); + cancelReportImpl(listener, receiver, primaryUser); + }, 0); + } catch (RemoteException ex) { + Log.e(TAG, "Remote died while trying to register death listener: " + rec.getUri()); + // First, remove from our list. + cancelReportImpl(listener, receiver, primaryUser); + } + + // Go tell Permission controller to start asking the user. + sendBroadcast(receiver, primaryUser); + } + + /** + * Cancel a pending report request (because of an explicit call to cancel) + */ + private void cancelReportImpl(IIncidentAuthListener listener) { + final int primaryUser = getAndValidateUser(); + final ComponentName receiver = getApproverComponent(primaryUser); + if (primaryUser != UserHandle.USER_NULL && receiver != null) { + cancelReportImpl(listener, receiver, primaryUser); + } + } + + /** + * Cancel a pending report request (either because of an explicit call to cancel + * by the calling app, or because of a binder death). + */ + private void cancelReportImpl(IIncidentAuthListener listener, ComponentName receiver, + int primaryUser) { + // First, remove from our list. + synchronized (mLock) { + removePendingReportRecLocked(listener); + } + // Second, call back to PermissionController to say it's canceled. + sendBroadcast(receiver, primaryUser); + } + + /** + * Send an extra copy of the broadcast, to tell them that the list has changed + * because of an addition or removal. This function is less aggressive than + * authorizeReportImpl in logging about failures, because this is for use in + * cleanup cases to keep the apps' list in sync with ours. + */ + private void sendBroadcast() { + final int primaryUser = getAndValidateUser(); + if (primaryUser == UserHandle.USER_NULL) { + return; + } + final ComponentName receiver = getApproverComponent(primaryUser); + if (receiver == null) { + return; + } + sendBroadcast(receiver, primaryUser); + } + + /** + * Send the confirmation broadcast. + */ + private void sendBroadcast(ComponentName receiver, int primaryUser) { + final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED); + intent.setComponent(receiver); + + // Send it to the primary user. + getContext().sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(primaryUser), + android.Manifest.permission.APPROVE_INCIDENT_REPORTS); + } + + /** + * Remove a PendingReportRec keyed by uri, and return it. + */ + private PendingReportRec findAndRemovePendingReportRecLocked(String uriString) { + final Uri uri = Uri.parse(uriString); + final int id; + try { + final String idStr = uri.getQueryParameter(IncidentManager.URI_PARAM_ID); + id = Integer.parseInt(idStr); + } catch (NumberFormatException ex) { + Log.w(TAG, "Can't parse id from: " + uriString); + return null; + } + final int size = mPending.size(); + for (int i = 0; i < size; i++) { + final PendingReportRec rec = mPending.get(i); + if (rec.id == id) { + mPending.remove(i); + return rec; + } + } + return null; + } + + /** + * Remove a PendingReportRec keyed by listener. + */ + private void removePendingReportRecLocked(IIncidentAuthListener listener) { + final int size = mPending.size(); + for (int i = 0; i < size; i++) { + final PendingReportRec rec = mPending.get(i); + if (rec.listener.asBinder() == listener.asBinder()) { + Log.i(TAG, " ...Removed PendingReportRec index=" + i + ": " + rec.getUri()); + mPending.remove(i); + } + } + } + + /** + * Just call listener.deny() (wrapping the RemoteException), without try to + * add it to the list. + */ + private void denyReportBeforeAddingRec(IIncidentAuthListener listener, String pkg) { + try { + listener.onReportDenied(); + } catch (RemoteException ex) { + Log.w(TAG, "Failed calling back for denial for " + pkg, ex); + } + } + + /** + * Check whether the current user is the primary user, and return the user id if they are. + * Returns UserHandle.USER_NULL if not valid. + */ + private int getAndValidateUser() { + // Current user + UserInfo currentUser; + try { + currentUser = ActivityManager.getService().getCurrentUser(); + } catch (RemoteException ex) { + // We're already inside the system process. + throw new RuntimeException(ex); + } + + // Primary user + final UserManager um = UserManager.get(getContext()); + final UserInfo primaryUser = um.getPrimaryUser(); + + // Check that we're using the right user. + if (currentUser == null) { + Log.w(TAG, "No current user. Nobody to approve the report." + + " The report will be denied."); + return UserHandle.USER_NULL; + } + if (primaryUser == null) { + Log.w(TAG, "No primary user. Nobody to approve the report." + + " The report will be denied."); + return UserHandle.USER_NULL; + } + if (primaryUser.id != currentUser.id) { + Log.w(TAG, "Only the primary user can approve bugreports, but they are not" + + " the current user. The report will be denied."); + return UserHandle.USER_NULL; + } + + return primaryUser.id; + } + + /** + * Return the ComponentName of the BroadcastReceiver that will approve reports. + * The system must have zero or one of these installed. We only look on the + * system partition. When the broadcast happens, the component will also need + * have the APPROVE_INCIDENT_REPORTS permission. + */ + private ComponentName getApproverComponent(int userId) { + // Find the one true BroadcastReceiver + final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED); + final List matches = mPackageManager.queryBroadcastReceiversAsUser(intent, + PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + if (matches.size() == 1) { + return matches.get(0).getComponentInfo().getComponentName(); + } else { + Log.w(TAG, "Didn't find exactly one BroadcastReceiver to handle " + + Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED + + ". The report will be denied. size=" + + matches.size() + ": matches=" + matches); + return null; + } + } + + /** + * Return whether the package is one of the packages installed for the uid. + */ + private boolean isPackageInUid(int uid, String packageName) { + try { + mAppOpsManager.checkPackage(uid, packageName); + return true; + } catch (SecurityException ex) { + return false; + } + } +} + diff --git a/services/core/java/com/android/server/incident/RequestQueue.java b/services/core/java/com/android/server/incident/RequestQueue.java new file mode 100644 index 0000000000000..85758e2efe38e --- /dev/null +++ b/services/core/java/com/android/server/incident/RequestQueue.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.server.incident; + +import android.os.Handler; +import android.os.IBinder; + +import java.util.ArrayList; + +/** + * Class to enqueue work until the system is ready. + */ +class RequestQueue { + /* + * All fields are protected by synchronized (mPending) + */ + + /** + * Requests that we can't start yet because system server isn't booted enough yet. + * Set to null when we have started. + */ + private ArrayList mPending = new ArrayList(); + + /** + * Where to run the requests. + */ + private final Handler mHandler; + + /** + * Whether someone has called start() yet. + */ + private boolean mStarted; + + /** + * Queue item. + */ + private class Rec { + /** + * Key for the record. + */ + public final IBinder key; + + /** + * True / false pairs will be elided by enqueue(). + */ + public final boolean value; + + /** + * The runnable to run. + */ + public final Runnable runnable; + + /** + * Constructor + */ + Rec(IBinder key, boolean value, Runnable runnable) { + this.key = key; + this.value = value; + this.runnable = runnable; + } + } + + /** + * Handler on the main thread. + */ + private final Runnable mWorker = new Runnable() { + @Override + public void run() { + ArrayList copy = null; + synchronized (mPending) { + if (mPending.size() > 0) { + copy = new ArrayList(mPending); + mPending.clear(); + } + } + if (copy != null) { + final int size = copy.size(); + for (int i = 0; i < size; i++) { + copy.get(i).runnable.run(); + } + } + } + }; + + /** + * Construct RequestQueue. + * + * @param handler Handler to use. + */ + RequestQueue(Handler handler) { + mHandler = handler; + } + + /** + * We're now ready to go. Start any previously pending runnables. + */ + public void start() { + synchronized (mPending) { + if (!mStarted) { + if (mPending.size() > 0) { + mHandler.post(mWorker); + } + mStarted = true; + } + } + } + + /** + * If we can run this now, then do it on the Handler provided in the constructor. + * If not, then enqueue it until start is called. + * + * The queue will elide keys with pairs of true/false values, so the user doesn't + * see confirmations that were previously canceled. + */ + public void enqueue(IBinder key, boolean value, Runnable runnable) { + synchronized (mPending) { + boolean skip = false; + if (!value) { + for (int i = mPending.size() - 1; i >= 0; i--) { + final Rec r = mPending.get(i); + if (r.key == key) { + if (r.value) { + skip = true; + mPending.remove(i); + break; + } + } + } + } + if (!skip) { + mPending.add(new Rec(key, value, runnable)); + } + if (mStarted) { + // Already started. Post now. + mHandler.post(mWorker); + } + } + } +} + diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 5861368026197..793818ca38a4d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -91,6 +91,7 @@ import com.android.server.display.DisplayManagerService; import com.android.server.dreams.DreamManagerService; import com.android.server.emergency.EmergencyAffordanceService; import com.android.server.hdmi.HdmiControlService; +import com.android.server.incident.IncidentCompanionService; import com.android.server.input.InputManagerService; import com.android.server.inputmethod.InputMethodManagerService; import com.android.server.inputmethod.MultiClientInputMethodManagerService; @@ -1856,6 +1857,11 @@ public final class SystemServer { mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class); traceEnd(); + // Incidentd and dumpstated helper + traceBeginAndSlog("StartIncidentCompanionService"); + mSystemServiceManager.startService(IncidentCompanionService.class); + traceEnd(); + if (safeMode) { traceBeginAndSlog("EnterSafeModeAndDisableJitCompilation"); mActivityManagerService.enterSafeMode();