Add PermissionManager exposing SPLIT_PERMISSIONS

The Permission Controller app (a mainline module) needs to be able to
read the SPLIT_PERMISSIONS. Hence this array needs to be exposed at
least as system-api. We need to make sure that the PackageParser,
PackageManager and Permission Controller app agree on which permissions
are split, hence it is best to define them at a single location.

I think exposing the split permissions to developers is useless and
potentially confusing. The app should never request a permission that
was split. The app should just behave as if split permissions do not
exist. The Permission Controller / Package Manager deal with the
split permissions and add them when needed. Hence I don't think we
should expose this data to 3rd parties.

Bug: 110953302
Test: requested permissions
Change-Id: I6951c52979c89ee5c13a4a14da125e1a01f2e234
diff --git a/api/system-current.txt b/api/system-current.txt
index 1009b67..aad5240 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -950,6 +950,7 @@
     field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
     field public static final java.lang.String NETWORK_SCORE_SERVICE = "network_score";
     field public static final java.lang.String OEM_LOCK_SERVICE = "oem_lock";
+    field public static final java.lang.String PERMISSION_SERVICE = "permission";
     field public static final java.lang.String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
     field public static final java.lang.String SECURE_ELEMENT_SERVICE = "secure_element";
     field public static final java.lang.String STATS_MANAGER = "stats";
@@ -4192,6 +4193,20 @@
 
 }
 
+package android.permission {
+
+  public final class PermissionManager {
+    method public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions();
+  }
+
+  public static final class PermissionManager.SplitPermissionInfo {
+    method public java.lang.String[] getNewPermissions();
+    method public java.lang.String getRootPermission();
+    method public int getTargetSdk();
+  }
+
+}
+
 package android.permissionpresenterservice {
 
   public abstract class RuntimePermissionPresenterService extends android.app.Service {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 41dec8f..0044005 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -134,6 +134,7 @@
 import android.os.Vibrator;
 import android.os.health.SystemHealthManager;
 import android.os.storage.StorageManager;
+import android.permission.PermissionManager;
 import android.print.IPrintManager;
 import android.print.PrintManager;
 import android.service.oemlock.IOemLockService;
@@ -1064,6 +1065,13 @@
                             throws ServiceNotFoundException {
                         return new TimeZoneDetector();
                     }});
+
+        registerService(Context.PERMISSION_SERVICE, PermissionManager.class,
+                new CachedServiceFetcher<PermissionManager>() {
+                    @Override
+                    public PermissionManager createService(ContextImpl ctx) {
+                        return new PermissionManager(ctx.getOuterContext());
+                    }});
     }
 
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 981be83..caaf4af 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3088,6 +3088,7 @@
             //@hide: SYSTEM_UPDATE_SERVICE,
             //@hide: TIME_DETECTOR_SERVICE,
             //@hide: TIME_ZONE_DETECTOR_SERVICE,
+            PERMISSION_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -3860,6 +3861,14 @@
      */
     public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
 
+    /**
+     * Official published name of the (internal) permission service.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    @SystemApi
+    public static final String PERMISSION_SERVICE = "permission";
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve an
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 03a3d1f..1fa5190 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -40,7 +40,6 @@
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 
-import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -73,6 +72,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.permission.PermissionManager;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.system.StructStat;
@@ -258,19 +258,6 @@
         }
     }
 
-    /** @hide */
-    public static class SplitPermissionInfo {
-        public final String rootPerm;
-        public final String[] newPerms;
-        public final int targetSdk;
-
-        public SplitPermissionInfo(String rootPerm, String[] newPerms, int targetSdk) {
-            this.rootPerm = rootPerm;
-            this.newPerms = newPerms;
-            this.targetSdk = targetSdk;
-        }
-    }
-
     /**
      * List of new permissions that have been added since 1.0.
      * NOTE: These must be declared in SDK version order, with permissions
@@ -290,34 +277,6 @@
     };
 
     /**
-     * List of permissions that have been split into more granular or dependent
-     * permissions.
-     * @hide
-     */
-    public static final PackageParser.SplitPermissionInfo SPLIT_PERMISSIONS[] =
-        new PackageParser.SplitPermissionInfo[] {
-            // READ_EXTERNAL_STORAGE is always required when an app requests
-            // WRITE_EXTERNAL_STORAGE, because we can't have an app that has
-            // write access without read access.  The hack here with the target
-            // target SDK version ensures that this grant is always done.
-            new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
-                    new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE },
-                    android.os.Build.VERSION_CODES.CUR_DEVELOPMENT+1),
-            new PackageParser.SplitPermissionInfo(android.Manifest.permission.READ_CONTACTS,
-                    new String[] { android.Manifest.permission.READ_CALL_LOG },
-                    android.os.Build.VERSION_CODES.JELLY_BEAN),
-            new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_CONTACTS,
-                    new String[] { android.Manifest.permission.WRITE_CALL_LOG },
-                    android.os.Build.VERSION_CODES.JELLY_BEAN),
-            new PackageParser.SplitPermissionInfo(Manifest.permission.ACCESS_FINE_LOCATION,
-                    new String[] { android.Manifest.permission.ACCESS_BACKGROUND_LOCATION },
-                    android.os.Build.VERSION_CODES.P0),
-            new PackageParser.SplitPermissionInfo(Manifest.permission.ACCESS_COARSE_LOCATION,
-                    new String[] { android.Manifest.permission.ACCESS_BACKGROUND_LOCATION },
-                    android.os.Build.VERSION_CODES.P0),
-    };
-
-    /**
      * @deprecated callers should move to explicitly passing around source path.
      */
     @Deprecated
@@ -2474,16 +2433,18 @@
             Slog.i(TAG, implicitPerms.toString());
         }
 
-        final int NS = PackageParser.SPLIT_PERMISSIONS.length;
+
+        final int NS = PermissionManager.SPLIT_PERMISSIONS.length;
         for (int is=0; is<NS; is++) {
-            final PackageParser.SplitPermissionInfo spi
-                    = PackageParser.SPLIT_PERMISSIONS[is];
-            if (pkg.applicationInfo.targetSdkVersion >= spi.targetSdk
-                    || !pkg.requestedPermissions.contains(spi.rootPerm)) {
+            final PermissionManager.SplitPermissionInfo spi =
+                    PermissionManager.SPLIT_PERMISSIONS[is];
+            if (pkg.applicationInfo.targetSdkVersion >= spi.getTargetSdk()
+                    || !pkg.requestedPermissions.contains(spi.getRootPermission())) {
                 continue;
             }
-            for (int in=0; in<spi.newPerms.length; in++) {
-                final String perm = spi.newPerms[in];
+            final String[] newPerms = spi.getNewPermissions();
+            for (int in = 0; in < newPerms.length; in++) {
+                final String perm = newPerms[in];
                 if (!pkg.requestedPermissions.contains(perm)) {
                     pkg.requestedPermissions.add(perm);
                 }
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
new file mode 100644
index 0000000..aa44eb7
--- /dev/null
+++ b/core/java/android/permission/PermissionManager.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.permission;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+
+import com.android.internal.annotations.Immutable;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * System level service for accessing the permission capabilities of the platform.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.PERMISSION_SERVICE)
+public final class PermissionManager {
+    /**
+     * {@link android.content.pm.PackageParser} needs access without having a {@link Context}.
+     *
+     * @hide
+     */
+    public static final SplitPermissionInfo[] SPLIT_PERMISSIONS = new SplitPermissionInfo[]{
+            // READ_EXTERNAL_STORAGE is always required when an app requests
+            // WRITE_EXTERNAL_STORAGE, because we can't have an app that has
+            // write access without read access.  The hack here with the target
+            // target SDK version ensures that this grant is always done.
+            new SplitPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                    new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE},
+                    android.os.Build.VERSION_CODES.CUR_DEVELOPMENT + 1),
+            new SplitPermissionInfo(android.Manifest.permission.READ_CONTACTS,
+                    new String[]{android.Manifest.permission.READ_CALL_LOG},
+                    android.os.Build.VERSION_CODES.JELLY_BEAN),
+            new SplitPermissionInfo(android.Manifest.permission.WRITE_CONTACTS,
+                    new String[]{android.Manifest.permission.WRITE_CALL_LOG},
+                    android.os.Build.VERSION_CODES.JELLY_BEAN),
+            new SplitPermissionInfo(Manifest.permission.ACCESS_FINE_LOCATION,
+                    new String[]{android.Manifest.permission.ACCESS_BACKGROUND_LOCATION},
+                    android.os.Build.VERSION_CODES.P0),
+            new SplitPermissionInfo(Manifest.permission.ACCESS_COARSE_LOCATION,
+                    new String[]{android.Manifest.permission.ACCESS_BACKGROUND_LOCATION},
+                    android.os.Build.VERSION_CODES.P0)};
+
+    private final @NonNull Context mContext;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param context The current context in which to operate.
+     * @hide
+     */
+    public PermissionManager(@NonNull Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Get list of permissions that have been split into more granular or dependent permissions.
+     *
+     * <p>E.g. before {@link android.os.Build.VERSION_CODES#P0} an app that was granted
+     * {@link Manifest.permission#ACCESS_COARSE_LOCATION} could access he location while it was in
+     * foreground and background. On platforms after {@link android.os.Build.VERSION_CODES#P0}
+     * the location permission only grants location access while the app is in foreground. This
+     * would break apps that target before {@link android.os.Build.VERSION_CODES#P0}. Hence whenever
+     * such an old app asks for a location permission (i.e. the
+     * {@link SplitPermissionInfo#getRootPermission()}), then the
+     * {@link Manifest.permission#ACCESS_BACKGROUND_LOCATION} permission (inside
+     * {@{@link SplitPermissionInfo#getNewPermissions}) is added.
+     *
+     * <p>Note: Regular apps do not have to worry about this. The platform and permission controller
+     * automatically add the new permissions where needed.
+     *
+     * @return All permissions that are split.
+     */
+    public @NonNull List<SplitPermissionInfo> getSplitPermissions() {
+        return Arrays.asList(SPLIT_PERMISSIONS);
+    }
+
+    /**
+     * A permission that was added in a previous API level might have split into several
+     * permissions. This object describes one such split.
+     */
+    @Immutable
+    public static final class SplitPermissionInfo {
+        private final @NonNull String mRootPerm;
+        private final @NonNull String[] mNewPerms;
+        private final int mTargetSdk;
+
+        /**
+         * Get the permission that is split.
+         */
+        public @NonNull String getRootPermission() {
+            return mRootPerm;
+        }
+
+        /**
+         * Get the permissions that are added.
+         */
+        public @NonNull String[] getNewPermissions() {
+            return mNewPerms;
+        }
+
+        /**
+         * Get the target API level when the permission was split.
+         */
+        public int getTargetSdk() {
+            return mTargetSdk;
+        }
+
+        private SplitPermissionInfo(@NonNull String rootPerm, @NonNull String[] newPerms,
+                int targetSdk) {
+            mRootPerm = rootPerm;
+            mNewPerms = newPerms;
+            mTargetSdk = targetSdk;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 91af0ec..fd09a278 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -232,6 +232,7 @@
 import android.os.storage.StorageManagerInternal;
 import android.os.storage.VolumeInfo;
 import android.os.storage.VolumeRecord;
+import android.permission.PermissionManager;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.security.KeyStore;
@@ -2912,13 +2913,15 @@
             if (mIsUpgrade) {
                 final int callingUid = getCallingUid();
 
-                final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length;
+                final List<PermissionManager.SplitPermissionInfo> splitPermissions =
+                        mContext.getSystemService(PermissionManager.class).getSplitPermissions();
+                final int numSplitPerms = splitPermissions.size();
                 for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
-                    final PackageParser.SplitPermissionInfo splitPerm =
-                            PackageParser.SPLIT_PERMISSIONS[splitPermNum];
-                    final String rootPerm = splitPerm.rootPerm;
+                    final PermissionManager.SplitPermissionInfo splitPerm =
+                            splitPermissions.get(splitPermNum);
+                    final String rootPerm = splitPerm.getRootPermission();
 
-                    if (preUpgradeSdkVersion >= splitPerm.targetSdk) {
+                    if (preUpgradeSdkVersion >= splitPerm.getTargetSdk()) {
                         continue;
                     }
 
@@ -2926,7 +2929,7 @@
                     for (int packageNum = 0; packageNum < numPackages; packageNum++) {
                         final PackageParser.Package pkg = mPackages.valueAt(packageNum);
 
-                        if (pkg.applicationInfo.targetSdkVersion >= splitPerm.targetSdk
+                        if (pkg.applicationInfo.targetSdkVersion >= splitPerm.getTargetSdk()
                                 || !pkg.requestedPermissions.contains(rootPerm)) {
                             continue;
                         }
@@ -2938,7 +2941,7 @@
                             continue;
                         }
 
-                        final String[] newPerms = splitPerm.newPerms;
+                        final String[] newPerms = splitPerm.getNewPermissions();
 
                         final int numNewPerms = newPerms.length;
                         for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 3c9dd63..6f644dd 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -34,7 +34,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.PackagesProvider;
 import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
-import android.content.pm.PackageParser;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.media.RingtoneManager;
@@ -48,6 +47,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.permission.PermissionManager;
 import android.print.PrintManager;
 import android.provider.CalendarContract;
 import android.provider.ContactsContract;
@@ -1024,15 +1024,17 @@
         ApplicationInfo applicationInfo = pkg.applicationInfo;
 
         // Automatically attempt to grant split permissions to older APKs
-        final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length;
+        final List<PermissionManager.SplitPermissionInfo> splitPermissions =
+                mContext.getSystemService(PermissionManager.class).getSplitPermissions();
+        final int numSplitPerms = splitPermissions.size();
         for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
-            final PackageParser.SplitPermissionInfo splitPerm =
-                    PackageParser.SPLIT_PERMISSIONS[splitPermNum];
+            final PermissionManager.SplitPermissionInfo splitPerm =
+                    splitPermissions.get(splitPermNum);
 
             if (applicationInfo != null
-                    && applicationInfo.targetSdkVersion < splitPerm.targetSdk
-                    && permissionsWithoutSplits.contains(splitPerm.rootPerm)) {
-                Collections.addAll(permissions, splitPerm.newPerms);
+                    && applicationInfo.targetSdkVersion < splitPerm.getTargetSdk()
+                    && permissionsWithoutSplits.contains(splitPerm.getRootPermission())) {
+                Collections.addAll(permissions, splitPerm.getNewPermissions());
             }
         }