Hide platform platform legacy permissions behind a menu option - package installer.

bug:21195624

Change-Id: If6de516d76969c3627316d091893da58f81af832
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml
index dedf0c4..0a39a43 100644
--- a/PermissionController/AndroidManifest.xml
+++ b/PermissionController/AndroidManifest.xml
@@ -81,6 +81,7 @@
                   android:theme="@style/Settings"
                   android:permission="android.permission.GRANT_REVOKE_PERMISSIONS">
             <intent-filter>
+                <action android:name="android.intent.action.MANAGE_PERMISSIONS" />
                 <action android:name="android.intent.action.MANAGE_APP_PERMISSIONS" />
                 <action android:name="android.intent.action.MANAGE_PERMISSION_APPS" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/PermissionController/res/drawable/ic_perm_device_info.xml b/PermissionController/res/drawable/ic_perm_device_info.xml
new file mode 100644
index 0000000..ef91c74
--- /dev/null
+++ b/PermissionController/res/drawable/ic_perm_device_info.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M26.0,14.0l-4.0,0.0l0.0,4.0l4.0,0.0l0.0,-4.0zm0.0,8.0l-4.0,0.0l0.0,12.0l4.0,0.0L26.0,22.0zm8.0,-19.98L14.0,2.0c-2.21,0.0 -4.0,1.79 -4.0,4.0l0.0,36.0c0.0,2.21 1.79,4.0 4.0,4.0l20.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L38.0,6.0c0.0,-2.21 -1.79,-3.98 -4.0,-3.98zM34.0,38.0L14.0,38.0L14.0,10.0l20.0,0.0l0.0,28.0z"/>
+</vector>
diff --git a/PermissionController/res/menu/toggle_legacy_permissions.xml b/PermissionController/res/menu/toggle_legacy_permissions.xml
new file mode 100644
index 0000000..544c479
--- /dev/null
+++ b/PermissionController/res/menu/toggle_legacy_permissions.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:id="@+id/toggle_legacy_permissions"
+        android:title="@string/show_legacy_permissions">
+    </item>
+
+</menu>
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml
index 38f7027..955c0e5 100644
--- a/PermissionController/res/values/strings.xml
+++ b/PermissionController/res/values/strings.xml
@@ -215,4 +215,10 @@
     <!-- Warning for turning off permissions on older apps -->
     <string name="old_sdk_deny_warning">This app was designed for an older version of Android. Denying permission may cause it to no longer function as intended.</string>
 
+    <!-- Label for the menu option to show legacy permissions [CHAR LIMIT=40] -->
+    <string name="show_legacy_permissions">Show legacy</string>
+
+    <!-- Label for the menu option to hide legacy permissions [CHAR LIMIT=40] -->
+    <string name="hide_legacy_permissions">Hide legacy</string>
+
 </resources>
diff --git a/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java b/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java
new file mode 100644
index 0000000..e3c9160
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.permission.model;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+
+import com.android.packageinstaller.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class AppPermissionGroup implements Comparable<AppPermissionGroup> {
+    private static final String PLATFORM_PACKAGE_NAME = "android";
+
+    private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
+
+    private final Context mContext;
+    private final UserHandle mUserHandle;
+    private final PackageManager mPackageManager;
+    private final AppOpsManager mAppOps;
+    private final ActivityManager mActivityManager;
+
+    private final PackageInfo mPackageInfo;
+    private final String mName;
+    private final String mDeclaringPackage;
+    private final CharSequence mLabel;
+    private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
+    private final String mIconPkg;
+    private final int mIconResId;
+
+    private final boolean mAppSupportsRuntimePermissions;
+
+    public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
+            String permissionName) {
+        PermissionInfo permissionInfo;
+        try {
+            permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+
+        if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
+            return null;
+        }
+
+        PackageItemInfo groupInfo = permissionInfo;
+        if (permissionInfo.group != null) {
+            try {
+                groupInfo = context.getPackageManager().getPermissionGroupInfo(
+                        permissionInfo.group, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                /* ignore */
+            }
+        }
+
+        List<PermissionInfo> permissionInfos = null;
+        if (groupInfo instanceof PermissionGroupInfo) {
+            try {
+                permissionInfos = context.getPackageManager().queryPermissionsByGroup(
+                        groupInfo.name, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                /* ignore */
+            }
+        }
+
+        return create(context, packageInfo, groupInfo, permissionInfos);
+    }
+
+    public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
+            PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos) {
+
+        AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name,
+                groupInfo.packageName, groupInfo.loadLabel(context.getPackageManager()),
+                groupInfo.packageName, groupInfo.icon);
+
+        if (groupInfo instanceof PermissionInfo) {
+            permissionInfos = new ArrayList<>();
+            permissionInfos.add((PermissionInfo) groupInfo);
+        }
+
+        if (permissionInfos == null || permissionInfos.isEmpty()) {
+            return null;
+        }
+
+        final int permissionCount = packageInfo.requestedPermissions.length;
+        for (int i = 0; i < permissionCount; i++) {
+            String requestedPermission = packageInfo.requestedPermissions[i];
+
+            PermissionInfo requestedPermissionInfo = null;
+
+            for (PermissionInfo permissionInfo : permissionInfos) {
+                if (requestedPermission.equals(permissionInfo.name)) {
+                    requestedPermissionInfo = permissionInfo;
+                    break;
+                }
+            }
+
+            if (requestedPermissionInfo == null) {
+                continue;
+            }
+
+            // Collect only runtime permissions.
+            if (requestedPermissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
+                continue;
+            }
+
+            // Don't allow toggle of non platform defined permissions for legacy apps via app ops.
+            if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
+                    && !PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)) {
+                continue;
+            }
+
+            final boolean granted = (packageInfo.requestedPermissionsFlags[i]
+                    & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
+
+            final int appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
+                    ? AppOpsManager.permissionToOpCode(requestedPermissionInfo.name)
+                    : AppOpsManager.OP_NONE;
+
+            final boolean appOpAllowed = appOp != AppOpsManager.OP_NONE
+                    && context.getSystemService(AppOpsManager.class).checkOp(appOp,
+                    packageInfo.applicationInfo.uid, packageInfo.packageName)
+                    == AppOpsManager.MODE_ALLOWED;
+
+            final int flags = context.getPackageManager().getPermissionFlags(
+                    requestedPermission, packageInfo.packageName,
+                    new UserHandle(context.getUserId()));
+
+            Permission permission = new Permission(requestedPermission, granted,
+                    appOp, appOpAllowed, flags);
+            group.addPermission(permission);
+        }
+
+        return group;
+    }
+
+    private AppPermissionGroup(Context context, PackageInfo packageInfo, String name,
+            String declaringPackage, CharSequence label, String iconPkg, int iconResId) {
+        mContext = context;
+        mUserHandle = new UserHandle(mContext.getUserId());
+        mPackageManager = mContext.getPackageManager();
+        mPackageInfo = packageInfo;
+        mAppSupportsRuntimePermissions = packageInfo.applicationInfo
+                .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
+        mAppOps = context.getSystemService(AppOpsManager.class);
+        mActivityManager = context.getSystemService(ActivityManager.class);
+        mDeclaringPackage = declaringPackage;
+        mName = name;
+        mLabel = label;
+        mIconPkg = iconPkg;
+        mIconResId = iconResId != 0 ? iconResId : R.drawable.ic_perm_device_info;
+    }
+
+    public boolean hasRuntimePermission() {
+        return mAppSupportsRuntimePermissions;
+    }
+
+    public boolean hasAppOpPermission() {
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            Permission permission = mPermissions.valueAt(i);
+            if (permission.getAppOp() != AppOpsManager.OP_NONE) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public PackageInfo getApp() {
+        return mPackageInfo;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getDeclaringPackage() {
+        return mDeclaringPackage;
+    }
+
+    public String getIconPkg() {
+        return mIconPkg;
+    }
+
+    public int getIconResId() {
+        return mIconResId;
+    }
+
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    public boolean hasPermission(String permission) {
+        return mPermissions.get(permission) != null;
+    }
+
+    public boolean areRuntimePermissionsGranted() {
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            Permission permission = mPermissions.valueAt(i);
+            if (mAppSupportsRuntimePermissions) {
+                if (!permission.isGranted()) {
+                    return false;
+                }
+            } else if (permission.isGranted() && permission.getAppOp()
+                    != AppOpsManager.OP_NONE && !permission.isAppOpAllowed()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean grantRuntimePermissions(boolean fixedByTheUser) {
+        final boolean isSharedUser = mPackageInfo.sharedUserId != null;
+        final int uid = mPackageInfo.applicationInfo.uid;
+
+        // We toggle permissions only to apps that support runtime
+        // permissions, otherwise we toggle the app op corresponding
+        // to the permission if the permission is granted to the app.
+        for (Permission permission : mPermissions.values()) {
+            if (mAppSupportsRuntimePermissions) {
+                // Grant the permission if needed.
+                if (!permission.isGranted()) {
+                    permission.setGranted(true);
+                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
+                            permission.getName(), new UserHandle(mContext.getUserId()));
+                }
+
+                // Update the permission flags.
+                if (!fixedByTheUser) {
+                    // Now the apps can ask for the permission as the user
+                    // no longer has it fixed in a denied state.
+                    if (permission.isUserFixed() || !permission.isUserSet()) {
+                        permission.setUserFixed(false);
+                        permission.setUserSet(true);
+                        mPackageManager.updatePermissionFlags(permission.getName(),
+                                mPackageInfo.packageName,
+                                PackageManager.FLAG_PERMISSION_USER_FIXED
+                                        | PackageManager.FLAG_PERMISSION_USER_SET,
+                                PackageManager.FLAG_PERMISSION_USER_SET,
+                                mUserHandle);
+                    }
+                }
+
+                // Enable the permission app op.
+                if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
+                    permission.setAppOpAllowed(true);
+                    mAppOps.setMode(permission.getAppOp(), android.os.Process.myUid(),
+                            mPackageInfo.packageName, AppOpsManager.MODE_ALLOWED);
+                }
+            } else {
+                // Legacy apps cannot have a not granted permission but just in case.
+                // Also if the permissions has no corresponding app op, then it is a
+                // third-party one and we do not offer toggling of such permissions.
+                if (!permission.isGranted() || !permission.hasAppOp()) {
+                    continue;
+                }
+
+                if (!permission.isAppOpAllowed()) {
+                    permission.setAppOpAllowed(true);
+                    // It this is a shared user we want to enable the app op for all
+                    // packages in the shared user to match the behavior of this
+                    // shared user having a runtime permission.
+                    if (isSharedUser) {
+                        // Enable the app op.
+                        String[] packageNames = mPackageManager.getPackagesForUid(uid);
+                        for (String packageName : packageNames) {
+                            mAppOps.setMode(permission.getAppOp(), uid, packageName,
+                                    AppOpsManager.MODE_ALLOWED);
+                        }
+                    } else {
+                        // Enable the app op.
+                        mAppOps.setMode(permission.getAppOp(), uid, mPackageInfo.packageName,
+                                AppOpsManager.MODE_ALLOWED);
+                    }
+
+                    // Mark that the permission should not be be granted on upgrade
+                    // when the app begins supporting runtime permissions.
+                    if (permission.shouldRevokeOnUpgrade()) {
+                        permission.setRevokeOnUpgrade(false);
+                        mPackageManager.updatePermissionFlags(permission.getName(),
+                                mPackageInfo.packageName,
+                                PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
+                                0, mUserHandle);
+                    }
+
+                    // Legacy apps do not know that they have to retry access to a
+                    // resource due to changes in runtime permissions (app ops in this
+                    // case). Therefore, we restart them on app op change, so they
+                    // can pick up the change.
+                    mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public boolean revokeRuntimePermissions(boolean fixedByTheUser) {
+        final boolean isSharedUser = mPackageInfo.sharedUserId != null;
+        final int uid = mPackageInfo.applicationInfo.uid;
+
+        // We toggle permissions only to apps that support runtime
+        // permissions, otherwise we toggle the app op corresponding
+        // to the permission if the permission is granted to the app.
+        for (Permission permission : mPermissions.values()) {
+            if (mAppSupportsRuntimePermissions) {
+                // Revoke the permission if needed.
+                if (permission.isGranted()) {
+                    permission.setGranted(false);
+                    mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
+                            permission.getName(), mUserHandle);
+                }
+
+                // Update the permission flags.
+                if (fixedByTheUser) {
+                    // Take a note that the user fixed the permission.
+                    if (permission.isUserSet() || !permission.isUserFixed()) {
+                        permission.setUserSet(false);
+                        permission.setUserFixed(true);
+                        mPackageManager.updatePermissionFlags(permission.getName(),
+                                mPackageInfo.packageName,
+                                PackageManager.FLAG_PERMISSION_USER_SET
+                                        | PackageManager.FLAG_PERMISSION_USER_FIXED,
+                                PackageManager.FLAG_PERMISSION_USER_FIXED,
+                                mUserHandle);
+                    }
+                } else {
+                    if (!permission.isUserSet()) {
+                        permission.setUserSet(true);
+                        // Take a note that the user already chose once.
+                        mPackageManager.updatePermissionFlags(permission.getName(),
+                                mPackageInfo.packageName,
+                                PackageManager.FLAG_PERMISSION_USER_SET,
+                                PackageManager.FLAG_PERMISSION_USER_SET,
+                                mUserHandle);
+                    }
+                }
+
+                // Disable the permission app op.
+                if (permission.hasAppOp() && permission.isAppOpAllowed()) {
+                    permission.setAppOpAllowed(false);
+                    mAppOps.setMode(permission.getAppOp(), android.os.Process.myUid(),
+                            mPackageInfo.packageName, AppOpsManager.MODE_IGNORED);
+                }
+            } else {
+                // Legacy apps cannot have a non-granted permission but just in case.
+                // Also if the permission has no corresponding app op, then it is a
+                // third-party one and we do not offer toggling of such permissions.
+                if (!permission.isGranted() || !permission.hasAppOp()) {
+                    continue;
+                }
+
+                if (permission.isAppOpAllowed()) {
+                    permission.setAppOpAllowed(false);
+                    // It this is a shared user we want to enable the app op for all
+                    // packages the the shared user to match the behavior of this
+                    // shared user having a runtime permission.
+                    if (isSharedUser) {
+                        String[] packageNames = mPackageManager.getPackagesForUid(uid);
+                        for (String packageName : packageNames) {
+                            // Disable the app op.
+                            mAppOps.setMode(permission.getAppOp(), uid,
+                                    packageName, AppOpsManager.MODE_IGNORED);
+                        }
+                    } else {
+                        // Disable the app op.
+                        mAppOps.setMode(permission.getAppOp(), uid,
+                                mPackageInfo.packageName, AppOpsManager.MODE_IGNORED);
+                    }
+
+                    // Mark that the permission should not be granted on upgrade
+                    // when the app begins supporting runtime permissions.
+                    if (!permission.shouldRevokeOnUpgrade()) {
+                        permission.setRevokeOnUpgrade(true);
+                        mPackageManager.updatePermissionFlags(permission.getName(),
+                                mPackageInfo.packageName,
+                                PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
+                                PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
+                                mUserHandle);
+                    }
+
+                    // Disabling an app op may put the app in a situation in which it
+                    // has a handle to state it shouldn't have, so we have to kill the
+                    // app. This matches the revoke runtime permission behavior.
+                    mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
+                }
+            }
+        }
+
+        return true;
+    }
+
+    public List<Permission> getPermissions() {
+        return new ArrayList<>(mPermissions.values());
+    }
+
+    public int getFlags() {
+        int flags = 0;
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            Permission permission = mPermissions.valueAt(i);
+            flags |= permission.getFlags();
+        }
+        return flags;
+    }
+
+    public boolean isUserFixed() {
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            Permission permission = mPermissions.valueAt(i);
+            if (!permission.isUserFixed()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean isPolicyFixed() {
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            Permission permission = mPermissions.valueAt(i);
+            if (permission.isPolicyFixed()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isUserSet() {
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            Permission permission = mPermissions.valueAt(i);
+            if (!permission.isUserSet()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean isSystemFixed() {
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            Permission permission = mPermissions.valueAt(i);
+            if (permission.isSystemFixed()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int compareTo(AppPermissionGroup another) {
+        final int result = mLabel.toString().compareTo(another.mLabel.toString());
+        if (result == 0) {
+            // Unbadged before badged.
+            return mPackageInfo.applicationInfo.uid
+                    - another.mPackageInfo.applicationInfo.uid;
+        }
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null) {
+            return false;
+        }
+
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+
+        AppPermissionGroup other = (AppPermissionGroup) obj;
+
+        if (mName == null) {
+            if (other.mName != null) {
+                return false;
+            }
+        } else if (!mName.equals(other.mName)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return mName != null ? mName.hashCode() : 0;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append(getClass().getSimpleName());
+        builder.append("{name=").append(mName);
+        if (!mPermissions.isEmpty()) {
+            builder.append(", <has permissions>}");
+        } else {
+            builder.append('}');
+        }
+        return builder.toString();
+    }
+
+    private void addPermission(Permission permission) {
+        mPermissions.put(permission.getName(), permission);
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissions.java b/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissions.java
index 8258126..1701477 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissions.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/model/AppPermissions.java
@@ -28,7 +28,7 @@
 import java.util.List;
 
 public final class AppPermissions {
-    private final ArrayMap<String, PermissionGroup> mGroups = new ArrayMap<>();
+    private final ArrayMap<String, AppPermissionGroup> mGroups = new ArrayMap<>();
 
     private final Context mContext;
 
@@ -63,11 +63,11 @@
         return mAppLabel;
     }
 
-    public PermissionGroup getPermissionGroup(String name) {
+    public AppPermissionGroup getPermissionGroup(String name) {
         return mGroups.get(name);
     }
 
-    public List<PermissionGroup> getPermissionGroups() {
+    public List<AppPermissionGroup> getPermissionGroups() {
         return new ArrayList<>(mGroups.values());
     }
 
@@ -84,7 +84,7 @@
     }
 
     private void loadPermissionGroups() {
-        List<PermissionGroup> groups = new ArrayList<>();
+        List<AppPermissionGroup> groups = new ArrayList<>();
 
         if (mPackageInfo.requestedPermissions == null) {
             return;
@@ -97,7 +97,7 @@
                 continue;
             }
 
-            PermissionGroup group = PermissionGroup.create(mContext,
+            AppPermissionGroup group = AppPermissionGroup.create(mContext,
                     mPackageInfo, requestedPerm);
             if (group == null) {
                 continue;
@@ -109,7 +109,7 @@
         if (!ArrayUtils.isEmpty(mFilterPermissions)) {
             final int groupCount = groups.size();
             for (int i = groupCount - 1; i >= 0; i--) {
-                PermissionGroup group = groups.get(i);
+                AppPermissionGroup group = groups.get(i);
                 boolean groupHasPermission = false;
                 for (String filterPerm : mFilterPermissions) {
                     if (group.hasPermission(filterPerm)) {
@@ -126,14 +126,14 @@
         Collections.sort(groups);
 
         mGroups.clear();
-        for (PermissionGroup group : groups) {
+        for (AppPermissionGroup group : groups) {
             mGroups.put(group.getName(), group);
         }
     }
 
     private static boolean hasGroupForPermission(String permission,
-            List<PermissionGroup> groups) {
-        for (PermissionGroup group : groups) {
+            List<AppPermissionGroup> groups) {
+        for (AppPermissionGroup group : groups) {
             if (group.hasPermission(permission)) {
                 return true;
             }
diff --git a/PermissionController/src/com/android/packageinstaller/permission/model/PermissionApps.java b/PermissionController/src/com/android/packageinstaller/permission/model/PermissionApps.java
index 10ec959..7191381 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/model/PermissionApps.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/model/PermissionApps.java
@@ -107,14 +107,14 @@
 
     public static class PermissionApp implements Comparable<PermissionApp> {
         private final String mPackageName;
-        private final PermissionGroup mPermissionGroup;
+        private final AppPermissionGroup mAppPermissionGroup;
         private final String mLabel;
         private final Drawable mIcon;
 
-        public PermissionApp(String packageName, PermissionGroup permissionGroup,
+        public PermissionApp(String packageName, AppPermissionGroup appPermissionGroup,
                 String label, Drawable icon) {
             mPackageName = packageName;
-            mPermissionGroup = permissionGroup;
+            mAppPermissionGroup = appPermissionGroup;
             mLabel = label;
             mIcon = icon;
         }
@@ -132,39 +132,39 @@
         }
 
         public boolean areRuntimePermissionsGranted() {
-            return mPermissionGroup.areRuntimePermissionsGranted();
+            return mAppPermissionGroup.areRuntimePermissionsGranted();
         }
 
         public void grantRuntimePermissions() {
-            mPermissionGroup.grantRuntimePermissions(false);
+            mAppPermissionGroup.grantRuntimePermissions(false);
         }
 
         public void revokeRuntimePermissions() {
-            mPermissionGroup.revokeRuntimePermissions(false);
+            mAppPermissionGroup.revokeRuntimePermissions(false);
         }
 
         public boolean isPolicyFixed() {
-            return mPermissionGroup.isPolicyFixed();
+            return mAppPermissionGroup.isPolicyFixed();
         }
 
         public boolean isSystemFixed() {
-            return mPermissionGroup.isSystemFixed();
+            return mAppPermissionGroup.isSystemFixed();
         }
 
         public boolean hasRuntimePermissions() {
-            return mPermissionGroup.hasRuntimePermission();
+            return mAppPermissionGroup.hasRuntimePermission();
         }
 
         public boolean hasAppOpPermissions() {
-            return mPermissionGroup.hasAppOpPermission();
+            return mAppPermissionGroup.hasAppOpPermission();
         }
 
         public String getPackageName() {
             return mPackageName;
         }
 
-        public PermissionGroup getPermissionGroup() {
-            return mPermissionGroup;
+        public AppPermissionGroup getPermissionGroup() {
+            return mAppPermissionGroup;
         }
 
         @Override
@@ -178,7 +178,7 @@
         }
 
         private int getUid() {
-            return mPermissionGroup.getApp().applicationInfo.uid;
+            return mAppPermissionGroup.getApp().applicationInfo.uid;
         }
     }
 
@@ -224,7 +224,7 @@
                             continue;
                         }
 
-                        PermissionGroup group = PermissionGroup.create(mContext,
+                        AppPermissionGroup group = AppPermissionGroup.create(mContext,
                                 app, groupInfo, groupPermInfos);
 
                         PermissionApp permApp = new PermissionApp(app.packageName,
diff --git a/PermissionController/src/com/android/packageinstaller/permission/model/PermissionGroup.java b/PermissionController/src/com/android/packageinstaller/permission/model/PermissionGroup.java
index 901f1a4..8a602b4 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/model/PermissionGroup.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/model/PermissionGroup.java
@@ -16,466 +16,41 @@
 
 package com.android.packageinstaller.permission.model;
 
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
-import android.os.Build;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-
-import java.util.ArrayList;
-import java.util.List;
+import android.graphics.drawable.Drawable;
 
 public final class PermissionGroup implements Comparable<PermissionGroup> {
-    private static final String PLATFORM_PACKAGE_NAME = "android";
-
-    private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
-
-    private final Context mContext;
-    private final UserHandle mUserHandle;
-    private final PackageManager mPackageManager;
-    private final AppOpsManager mAppOps;
-    private final ActivityManager mActivityManager;
-
-    private final PackageInfo mPackageInfo;
     private final String mName;
+    private final String mDeclaringPackage;
     private final CharSequence mLabel;
-    private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
-    private final String mIconPkg;
-    private final int mIconResId;
+    private final Drawable mIcon;
 
-    private final boolean mAppSupportsRuntimePermissions;
-
-    public static PermissionGroup create(Context context, PackageInfo packageInfo,
-            String permissionName) {
-        PermissionInfo permissionInfo;
-        try {
-            permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            return null;
-        }
-
-        if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
-            return null;
-        }
-
-        PackageItemInfo groupInfo = permissionInfo;
-        if (permissionInfo.group != null) {
-            try {
-                groupInfo = context.getPackageManager().getPermissionGroupInfo(
-                        permissionInfo.group, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                /* ignore */
-            }
-        }
-
-        List<PermissionInfo> permissionInfos = null;
-        if (groupInfo instanceof PermissionGroupInfo) {
-            try {
-                permissionInfos = context.getPackageManager().queryPermissionsByGroup(
-                        groupInfo.name, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                /* ignore */
-            }
-        }
-
-        return create(context, packageInfo, groupInfo, permissionInfos);
-
-    }
-
-    public static PermissionGroup create(Context context, PackageInfo packageInfo,
-            PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos) {
-
-        PermissionGroup group = new PermissionGroup(context, packageInfo, groupInfo.name,
-                groupInfo.loadLabel(context.getPackageManager()), groupInfo.packageName,
-                groupInfo.icon);
-
-        if (groupInfo instanceof PermissionInfo) {
-            permissionInfos = new ArrayList<>();
-            permissionInfos.add((PermissionInfo) groupInfo);
-        }
-
-        if (permissionInfos == null || permissionInfos.isEmpty()) {
-            return null;
-        }
-
-        final int permissionCount = packageInfo.requestedPermissions.length;
-        for (int i = 0; i < permissionCount; i++) {
-            String requestedPermission = packageInfo.requestedPermissions[i];
-
-            PermissionInfo requestedPermissionInfo = null;
-
-            for (PermissionInfo permissionInfo : permissionInfos) {
-                if (requestedPermission.equals(permissionInfo.name)) {
-                    requestedPermissionInfo = permissionInfo;
-                    break;
-                }
-            }
-
-            if (requestedPermissionInfo == null) {
-                continue;
-            }
-
-            // Collect only runtime permissions.
-            if (requestedPermissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
-                continue;
-            }
-
-            // Don't allow toggle of non platform defined permissions for legacy apps via app ops.
-            if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
-                    && !PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)) {
-                continue;
-            }
-
-            final boolean granted = (packageInfo.requestedPermissionsFlags[i]
-                    & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
-
-            final int appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
-                    ? AppOpsManager.permissionToOpCode(requestedPermissionInfo.name)
-                    : AppOpsManager.OP_NONE;
-
-            final boolean appOpAllowed = appOp != AppOpsManager.OP_NONE
-                    && context.getSystemService(AppOpsManager.class).checkOp(appOp,
-                    packageInfo.applicationInfo.uid, packageInfo.packageName)
-                    == AppOpsManager.MODE_ALLOWED;
-
-            final int flags = context.getPackageManager().getPermissionFlags(
-                    requestedPermission, packageInfo.packageName,
-                    new UserHandle(context.getUserId()));
-
-            Permission permission = new Permission(requestedPermission, granted,
-                    appOp, appOpAllowed, flags);
-            group.addPermission(permission);
-        }
-
-        return group;
-    }
-
-    private PermissionGroup(Context context, PackageInfo packageInfo, String name,
-            CharSequence label, String iconPkg, int iconResId) {
-        mContext = context;
-        mUserHandle = new UserHandle(mContext.getUserId());
-        mPackageManager = mContext.getPackageManager();
-        mPackageInfo = packageInfo;
-        mAppSupportsRuntimePermissions = packageInfo.applicationInfo
-                .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
-        mAppOps = context.getSystemService(AppOpsManager.class);
-        mActivityManager = context.getSystemService(ActivityManager.class);
+    PermissionGroup(String name, String declaringPackage,
+            CharSequence label, Drawable icon) {
+        mDeclaringPackage = declaringPackage;
         mName = name;
         mLabel = label;
-        mIconPkg = iconPkg;
-        mIconResId = iconResId != 0 ? iconResId
-                : com.android.internal.R.drawable.ic_perm_device_info;
-    }
-
-    public boolean hasRuntimePermission() {
-        return mAppSupportsRuntimePermissions;
-    }
-
-    public boolean hasAppOpPermission() {
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            Permission permission = mPermissions.valueAt(i);
-            if (permission.getAppOp() != AppOpsManager.OP_NONE) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public PackageInfo getApp() {
-        return mPackageInfo;
+        mIcon = icon;
     }
 
     public String getName() {
         return mName;
     }
 
-    public String getIconPkg() {
-        return mIconPkg;
-    }
-
-    public int getIconResId() {
-        return mIconResId;
+    public String getDeclaringPackage() {
+        return mDeclaringPackage;
     }
 
     public CharSequence getLabel() {
         return mLabel;
     }
 
-    public boolean hasPermission(String permission) {
-        return mPermissions.get(permission) != null;
-    }
-
-    public boolean areRuntimePermissionsGranted() {
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            Permission permission = mPermissions.valueAt(i);
-            if (mAppSupportsRuntimePermissions) {
-                if (!permission.isGranted()) {
-                    return false;
-                }
-            } else if (permission.isGranted() && permission.getAppOp()
-                    != AppOpsManager.OP_NONE && !permission.isAppOpAllowed()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public boolean grantRuntimePermissions(boolean fixedByTheUser) {
-        final boolean isSharedUser = mPackageInfo.sharedUserId != null;
-        final int uid = mPackageInfo.applicationInfo.uid;
-
-        // We toggle permissions only to apps that support runtime
-        // permissions, otherwise we toggle the app op corresponding
-        // to the permission if the permission is granted to the app.
-        for (Permission permission : mPermissions.values()) {
-            if (mAppSupportsRuntimePermissions) {
-                // Grant the permission if needed.
-                if (!permission.isGranted()) {
-                    permission.setGranted(true);
-                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
-                            permission.getName(), new UserHandle(mContext.getUserId()));
-                }
-
-                // Update the permission flags.
-                if (!fixedByTheUser) {
-                    // Now the apps can ask for the permission as the user
-                    // no longer has it fixed in a denied state.
-                    if (permission.isUserFixed() || !permission.isUserSet()) {
-                        permission.setUserFixed(false);
-                        permission.setUserSet(true);
-                        mPackageManager.updatePermissionFlags(permission.getName(),
-                                mPackageInfo.packageName,
-                                PackageManager.FLAG_PERMISSION_USER_FIXED
-                                        | PackageManager.FLAG_PERMISSION_USER_SET,
-                                PackageManager.FLAG_PERMISSION_USER_SET,
-                                mUserHandle);
-                    }
-                }
-
-                // Enable the permission app op.
-                if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
-                    permission.setAppOpAllowed(true);
-                    mAppOps.setMode(permission.getAppOp(), android.os.Process.myUid(),
-                            mPackageInfo.packageName, AppOpsManager.MODE_ALLOWED);
-                }
-            } else {
-                // Legacy apps cannot have a not granted permission but just in case.
-                // Also if the permissions has no corresponding app op, then it is a
-                // third-party one and we do not offer toggling of such permissions.
-                if (!permission.isGranted() || !permission.hasAppOp()) {
-                    continue;
-                }
-
-                if (!permission.isAppOpAllowed()) {
-                    permission.setAppOpAllowed(true);
-                    // It this is a shared user we want to enable the app op for all
-                    // packages in the shared user to match the behavior of this
-                    // shared user having a runtime permission.
-                    if (isSharedUser) {
-                        // Enable the app op.
-                        String[] packageNames = mPackageManager.getPackagesForUid(uid);
-                        for (String packageName : packageNames) {
-                            mAppOps.setMode(permission.getAppOp(), uid, packageName,
-                                    AppOpsManager.MODE_ALLOWED);
-                        }
-                    } else {
-                        // Enable the app op.
-                        mAppOps.setMode(permission.getAppOp(), uid, mPackageInfo.packageName,
-                                AppOpsManager.MODE_ALLOWED);
-                    }
-
-                    // Mark that the permission should not be be granted on upgrade
-                    // when the app begins supporting runtime permissions.
-                    if (permission.shouldRevokeOnUpgrade()) {
-                        permission.setRevokeOnUpgrade(false);
-                        mPackageManager.updatePermissionFlags(permission.getName(),
-                                mPackageInfo.packageName,
-                                PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
-                                0, mUserHandle);
-                    }
-
-                    // Legacy apps do not know that they have to retry access to a
-                    // resource due to changes in runtime permissions (app ops in this
-                    // case). Therefore, we restart them on app op change, so they
-                    // can pick up the change.
-                    mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
-                }
-            }
-        }
-
-        return true;
-    }
-
-    public boolean revokeRuntimePermissions(boolean fixedByTheUser) {
-        final boolean isSharedUser = mPackageInfo.sharedUserId != null;
-        final int uid = mPackageInfo.applicationInfo.uid;
-
-        // We toggle permissions only to apps that support runtime
-        // permissions, otherwise we toggle the app op corresponding
-        // to the permission if the permission is granted to the app.
-        for (Permission permission : mPermissions.values()) {
-            if (mAppSupportsRuntimePermissions) {
-                // Revoke the permission if needed.
-                if (permission.isGranted()) {
-                    permission.setGranted(false);
-                    mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
-                            permission.getName(), mUserHandle);
-                }
-
-                // Update the permission flags.
-                if (fixedByTheUser) {
-                    // Take a note that the user fixed the permission.
-                    if (permission.isUserSet() || !permission.isUserFixed()) {
-                        permission.setUserSet(false);
-                        permission.setUserFixed(true);
-                        mPackageManager.updatePermissionFlags(permission.getName(),
-                                mPackageInfo.packageName,
-                                PackageManager.FLAG_PERMISSION_USER_SET
-                                        | PackageManager.FLAG_PERMISSION_USER_FIXED,
-                                PackageManager.FLAG_PERMISSION_USER_FIXED,
-                                mUserHandle);
-                    }
-                } else {
-                    if (!permission.isUserSet()) {
-                        permission.setUserSet(true);
-                        // Take a note that the user already chose once.
-                        mPackageManager.updatePermissionFlags(permission.getName(),
-                                mPackageInfo.packageName,
-                                PackageManager.FLAG_PERMISSION_USER_SET,
-                                PackageManager.FLAG_PERMISSION_USER_SET,
-                                mUserHandle);
-                    }
-                }
-
-                // Disable the permission app op.
-                if (permission.hasAppOp() && permission.isAppOpAllowed()) {
-                    permission.setAppOpAllowed(false);
-                    mAppOps.setMode(permission.getAppOp(), android.os.Process.myUid(),
-                            mPackageInfo.packageName, AppOpsManager.MODE_IGNORED);
-                }
-            } else {
-                // Legacy apps cannot have a non-granted permission but just in case.
-                // Also if the permission has no corresponding app op, then it is a
-                // third-party one and we do not offer toggling of such permissions.
-                if (!permission.isGranted() || !permission.hasAppOp()) {
-                    continue;
-                }
-
-                if (permission.isAppOpAllowed()) {
-                    permission.setAppOpAllowed(false);
-                    // It this is a shared user we want to enable the app op for all
-                    // packages the the shared user to match the behavior of this
-                    // shared user having a runtime permission.
-                    if (isSharedUser) {
-                        String[] packageNames = mPackageManager.getPackagesForUid(uid);
-                        for (String packageName : packageNames) {
-                            // Disable the app op.
-                            mAppOps.setMode(permission.getAppOp(), uid,
-                                    packageName, AppOpsManager.MODE_IGNORED);
-                        }
-                    } else {
-                        // Disable the app op.
-                        mAppOps.setMode(permission.getAppOp(), uid,
-                                mPackageInfo.packageName, AppOpsManager.MODE_IGNORED);
-                    }
-
-                    // Mark that the permission should not be granted on upgrade
-                    // when the app begins supporting runtime permissions.
-                    if (!permission.shouldRevokeOnUpgrade()) {
-                        permission.setRevokeOnUpgrade(true);
-                        mPackageManager.updatePermissionFlags(permission.getName(),
-                                mPackageInfo.packageName,
-                                PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
-                                PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
-                                mUserHandle);
-                    }
-
-                    // Disabling an app op may put the app in a situation in which it
-                    // has a handle to state it shouldn't have, so we have to kill the
-                    // app. This matches the revoke runtime permission behavior.
-                    mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
-                }
-            }
-        }
-
-        return true;
-    }
-
-    public List<Permission> getPermissions() {
-        return new ArrayList<>(mPermissions.values());
-    }
-
-    public int getFlags() {
-        int flags = 0;
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            Permission permission = mPermissions.valueAt(i);
-            flags |= permission.getFlags();
-        }
-        return flags;
-    }
-
-    public boolean isUserFixed() {
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            Permission permission = mPermissions.valueAt(i);
-            if (!permission.isUserFixed()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public boolean isPolicyFixed() {
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            Permission permission = mPermissions.valueAt(i);
-            if (permission.isPolicyFixed()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean isUserSet() {
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            Permission permission = mPermissions.valueAt(i);
-            if (!permission.isUserSet()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public boolean isSystemFixed() {
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            Permission permission = mPermissions.valueAt(i);
-            if (permission.isSystemFixed()) {
-                return true;
-            }
-        }
-        return false;
+    public Drawable getIcon() {
+        return mIcon;
     }
 
     @Override
     public int compareTo(PermissionGroup another) {
-        final int result = mLabel.toString().compareTo(another.mLabel.toString());
-        if (result == 0) {
-            // Unbadged before badged.
-            return mPackageInfo.applicationInfo.uid
-                    - another.mPackageInfo.applicationInfo.uid;
-        }
-        return result;
+        return mLabel.toString().compareTo(another.mLabel.toString());
     }
 
     @Override
@@ -509,22 +84,4 @@
     public int hashCode() {
         return mName != null ? mName.hashCode() : 0;
     }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(getClass().getSimpleName());
-        builder.append("{name=").append(mName);
-        if (!mPermissions.isEmpty()) {
-            builder.append(", <has permissions>}");
-        } else {
-            builder.append('}');
-        }
-        return builder.toString();
-    }
-
-    private void addPermission(Permission permission) {
-        mPermissions.put(permission.getName(), permission);
-    }
 }
-
diff --git a/PermissionController/src/com/android/packageinstaller/permission/model/PermissionGroups.java b/PermissionController/src/com/android/packageinstaller/permission/model/PermissionGroups.java
new file mode 100644
index 0000000..62477b8
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/model/PermissionGroups.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.permission.model;
+
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.Loader;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.ArraySet;
+
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public final class PermissionGroups implements LoaderCallbacks<List<PermissionGroup>> {
+    private final ArrayList<PermissionGroup> mGroups = new ArrayList<>();
+    private final Context mContext;
+    private final LoaderManager mLoaderManager;
+    private final PermissionsGroupsChangeCallback mCallback;
+
+    public interface PermissionsGroupsChangeCallback {
+        public void onPermissionGroupsChanged();
+    }
+
+    public PermissionGroups(Context context, LoaderManager loaderManager,
+            PermissionsGroupsChangeCallback callback) {
+        mContext = context;
+        mLoaderManager = loaderManager;
+        mCallback = callback;
+    }
+
+    @Override
+    public Loader<List<PermissionGroup>> onCreateLoader(int id, Bundle args) {
+        return new PermissionsLoader(mContext);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<List<PermissionGroup>> loader,
+            List<PermissionGroup> groups) {
+        if (mGroups.equals(groups)) {
+            return;
+        }
+        mGroups.clear();
+        mGroups.addAll(groups);
+        mCallback.onPermissionGroupsChanged();
+    }
+
+    @Override
+    public void onLoaderReset(Loader<List<PermissionGroup>> loader) {
+        mGroups.clear();
+        mCallback.onPermissionGroupsChanged();
+    }
+
+    public void refresh() {
+        mLoaderManager.restartLoader(0, null, this);
+        mLoaderManager.getLoader(0).forceLoad();
+    }
+
+    public List<PermissionGroup> getGroups() {
+        return mGroups;
+    }
+
+    public PermissionGroup getGroup(String name) {
+        for (PermissionGroup group : mGroups) {
+            if (group.getName().equals(name)) {
+                return group;
+            }
+        }
+        return null;
+    }
+
+    private static final class PermissionsLoader extends AsyncTaskLoader<List<PermissionGroup>> {
+
+        public PermissionsLoader(Context context) {
+            super(context);
+        }
+
+        @Override
+        public List<PermissionGroup> loadInBackground() {
+            List<PermissionGroup> groups = new ArrayList<>();
+            Set<String> seenPermissions = new ArraySet<>();
+
+
+            PackageManager packageManager = getContext().getPackageManager();
+            List<PermissionGroupInfo> groupInfos = packageManager.getAllPermissionGroups(0);
+
+            for (PermissionGroupInfo groupInfo : groupInfos) {
+                // Mare sure we respond to cancellation.
+                if (isLoadInBackgroundCanceled()) {
+                    return Collections.emptyList();
+                }
+
+                // Get the permissions in this group.
+                final List<PermissionInfo> groupPermissions;
+                try {
+                    groupPermissions = packageManager.queryPermissionsByGroup(groupInfo.name, 0);
+                } catch (PackageManager.NameNotFoundException e) {
+                    continue;
+                }
+
+                boolean hasRuntimePermissions = false;
+
+                // Cache seen permissions and see if group has runtime permissions.
+                for (PermissionInfo groupPermission : groupPermissions) {
+                    seenPermissions.add(groupPermission.name);
+                    if (groupPermission.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) {
+                        hasRuntimePermissions = true;
+                    }
+                }
+
+                // No runtime permissions - not interesting for us.
+                if (!hasRuntimePermissions) {
+                    continue;
+                }
+
+                CharSequence label = loadItemInfoLabel(groupInfo);
+                Drawable icon = loadItemInfoIcon(groupInfo);
+
+                // Create the group and add to the list.
+                PermissionGroup group = new PermissionGroup(groupInfo.name,
+                        groupInfo.packageName, label, icon);
+                groups.add(group);
+            }
+
+
+            // Make sure we add groups for lone runtime permissions.
+            List<PackageInfo> installedPackages = getContext().getPackageManager()
+                    .getInstalledPackages(PackageManager.GET_PERMISSIONS);
+
+
+            // We will filter out permissions that no package requests.
+            Set<String> requestedPermissions = new ArraySet<>();
+            for (PackageInfo installedPackage : installedPackages) {
+                if (installedPackage.requestedPermissions == null) {
+                    break;
+                }
+                for (String requestedPermission : installedPackage.requestedPermissions) {
+                    requestedPermissions.add(requestedPermission);
+                }
+            }
+
+            for (PackageInfo installedPackage : installedPackages) {
+                if (installedPackage.permissions == null) {
+                    continue;
+                }
+
+                for (PermissionInfo permissionInfo : installedPackage.permissions) {
+                    // If we have handled this permission, no more work to do.
+                    if (!seenPermissions.add(permissionInfo.name)) {
+                        continue;
+                    }
+
+                    // We care only about runtime permissions.
+                    if (permissionInfo.protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
+                        continue;
+                    }
+
+                    // If no app uses this permission,
+                    if (!requestedPermissions.contains(permissionInfo.name)) {
+                        continue;
+                    }
+
+                    CharSequence label = loadItemInfoLabel(permissionInfo);
+                    Drawable icon = loadItemInfoIcon(permissionInfo);
+
+                    // Create the group and add to the list.
+                    PermissionGroup group = new PermissionGroup(permissionInfo.name,
+                            permissionInfo.packageName, label, icon);
+                    groups.add(group);
+                }
+            }
+
+            Collections.sort(groups);
+            return groups;
+        }
+
+        private CharSequence loadItemInfoLabel(PackageItemInfo itemInfo) {
+            CharSequence label = itemInfo.loadLabel(getContext().getPackageManager());
+            if (label == null) {
+                label = itemInfo.name;
+            }
+            return label;
+        }
+
+        private Drawable loadItemInfoIcon(PackageItemInfo itemInfo) {
+            final Drawable icon;
+            if (itemInfo.icon > 0) {
+                icon = Utils.loadDrawable(getContext().getPackageManager(),
+                        itemInfo.packageName, itemInfo.icon);
+            } else {
+                icon = getContext().getDrawable(R.drawable.ic_perm_device_info);
+            }
+            return icon;
+        }
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java b/PermissionController/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java
index e1dd85a..79e6624 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/AppPermissionsFragment.java
@@ -38,6 +38,8 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
@@ -47,8 +49,8 @@
 import android.widget.Toast;
 
 import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.model.AppPermissionGroup;
 import com.android.packageinstaller.permission.model.AppPermissions;
-import com.android.packageinstaller.permission.model.PermissionGroup;
 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
 import com.android.packageinstaller.permission.utils.Utils;
 
@@ -64,11 +66,12 @@
 
     private static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
 
-    private List<PermissionGroup> mToggledGroups;
+    private List<AppPermissionGroup> mToggledGroups;
     private AppPermissions mAppPermissions;
     private PreferenceScreen mExtraScreen;
 
     private boolean mHasConfirmedRevoke;
+    private boolean mShowLegacyPermissions;
 
     public static AppPermissionsFragment newInstance(String packageName) {
         AppPermissionsFragment instance = new AppPermissionsFragment();
@@ -98,9 +101,16 @@
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
-            case android.R.id.home:
+            case android.R.id.home: {
                 getActivity().finish();
                 return true;
+            }
+
+            case R.id.toggle_legacy_permissions: {
+                mShowLegacyPermissions = !mShowLegacyPermissions;
+                updatePermissionsUi();
+                return true;
+            }
         }
         return super.onOptionsItemSelected(item);
     }
@@ -124,6 +134,23 @@
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         bindUi();
+        updatePermissionsUi();
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.toggle_legacy_permissions, menu);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        MenuItem item = menu.findItem(R.id.toggle_legacy_permissions);
+        if (!mShowLegacyPermissions) {
+            item.setTitle(R.string.show_legacy_permissions);
+        } else {
+            item.setTitle(R.string.hide_legacy_permissions);
+        }
     }
 
     private void bindUi() {
@@ -162,7 +189,6 @@
             breadcrumbView.setText(label);
         }
 
-        PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(activity);
         mAppPermissions = new AppPermissions(activity, packageInfo, null, new Runnable() {
             @Override
             public void run() {
@@ -170,11 +196,26 @@
             }
         });
 
+        PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(activity);
+        setPreferenceScreen(screen);
+    }
+
+    private void updatePermissionsUi() {
+        final Activity activity = getActivity();
+
+        if (activity == null) {
+            return;
+        }
+
+        final PreferenceScreen screen = getPreferenceScreen();
+        screen.removeAll();
+        mExtraScreen = null;
+
         final Preference extraPerms = new Preference(activity);
         extraPerms.setIcon(R.drawable.ic_toc);
         extraPerms.setTitle(R.string.additional_permissions);
 
-        for (PermissionGroup group : mAppPermissions.getPermissionGroups()) {
+        for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
             // We currently will not show permissions fixed by the system
             // which is what the system does for system components.
             if (group.isSystemFixed()) {
@@ -188,14 +229,20 @@
                 continue;
             }
 
+            // Show legacy permissions only if the user chose that.
+            if (!mShowLegacyPermissions && !Utils.isModernPermissionGroup(group.getName())) {
+                continue;
+            }
+
             SwitchPreference preference = new SwitchPreference(activity);
             preference.setOnPreferenceChangeListener(this);
             preference.setKey(group.getName());
-            preference.setIcon(Utils.loadDrawable(pm, group.getIconPkg(),
-                    group.getIconResId()));
+            preference.setIcon(Utils.loadDrawable(activity.getPackageManager(),
+                    group.getIconPkg(), group.getIconResId()));
             preference.setTitle(group.getLabel());
             preference.setPersistent(false);
             preference.setEnabled(!group.isPolicyFixed());
+
             if (group.getIconPkg().equals(OS_PKG)) {
                 screen.addPreference(preference);
             } else {
@@ -205,6 +252,7 @@
                 mExtraScreen.addPreference(preference);
             }
         }
+
         if (mExtraScreen != null) {
             extraPerms.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                 @Override
@@ -222,14 +270,12 @@
                     mExtraScreen.getPreferenceCount()));
             screen.addPreference(extraPerms);
         }
-
-        setPreferenceScreen(screen);
     }
 
     @Override
     public boolean onPreferenceChange(final Preference preference, Object newValue) {
         String groupName = preference.getKey();
-        final PermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
+        final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
 
         if (group == null) {
             return false;
@@ -269,7 +315,7 @@
         logToggledGroups();
     }
 
-    private void addToggledGroup(PermissionGroup group) {
+    private void addToggledGroup(AppPermissionGroup group) {
         if (mToggledGroups == null) {
             mToggledGroups = new ArrayList<>();
         }
@@ -304,7 +350,7 @@
             Preference preference = screen.getPreference(i);
             if (preference instanceof SwitchPreference) {
                 SwitchPreference switchPref = (SwitchPreference) preference;
-                PermissionGroup group = mAppPermissions
+                AppPermissionGroup group = mAppPermissions
                         .getPermissionGroup(switchPref.getKey());
                 if (group != null) {
                     switchPref.setChecked(group.areRuntimePermissionsGranted());
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
index b406db1..6f824ea 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java
@@ -34,9 +34,9 @@
 import android.util.SparseArray;
 
 import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.model.AppPermissionGroup;
 import com.android.packageinstaller.permission.model.AppPermissions;
 import com.android.packageinstaller.permission.model.Permission;
-import com.android.packageinstaller.permission.model.PermissionGroup;
 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
 
 import java.util.ArrayList;
@@ -100,7 +100,7 @@
                     }
                 });
 
-        for (PermissionGroup group : mAppPermissions.getPermissionGroups()) {
+        for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
             // We allow the user to choose only non-fixed permissions. A permission
             // is fixed either by device policy or the user denying with prejudice.
             if (!group.areRuntimePermissionsGranted() &&
@@ -201,7 +201,7 @@
         }
     }
 
-    private void updateGrantResults(PermissionGroup group) {
+    private void updateGrantResults(AppPermissionGroup group) {
         for (Permission permission : group.getPermissions()) {
             if (permission.isGranted()) {
                 final int index = ArrayUtils.getArrayIndex(
@@ -307,7 +307,7 @@
         }
 
         final int groupCount = mRequestGrantPermissionGroups.size();
-        List<PermissionGroup> groups = new ArrayList<>(groupCount);
+        List<AppPermissionGroup> groups = new ArrayList<>(groupCount);
         for (int i = 0; i < groupCount; i++) {
             groups.add(mRequestGrantPermissionGroups.valueAt(i).mGroup);
         }
@@ -320,10 +320,10 @@
         public static final int STATE_ALLOWED = 1;
         public static final int STATE_DENIED = 2;
 
-        public final PermissionGroup mGroup;
+        public final AppPermissionGroup mGroup;
         public int mState = STATE_UNKNOWN;
 
-        public GroupState(PermissionGroup group) {
+        public GroupState(AppPermissionGroup group) {
             mGroup = group;
         }
     }
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java
index ef117a9..2fc1a02 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/ManagePermissionsActivity.java
@@ -31,26 +31,37 @@
 
         Fragment fragment = null;
         String action = getIntent().getAction();
-        if (Intent.ACTION_MANAGE_APP_PERMISSIONS.equals(action)) {
-            String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-            if (packageName == null) {
-                Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PACKAGE_NAME");
+
+        switch (action) {
+            case Intent.ACTION_MANAGE_PERMISSIONS: {
+                fragment = ManagePermissionsFragment.newInstance();
+            } break;
+
+            case Intent.ACTION_MANAGE_APP_PERMISSIONS: {
+                String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                if (packageName == null) {
+                    Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PACKAGE_NAME");
+                    finish();
+                    return;
+                }
+                fragment = AppPermissionsFragment.newInstance(packageName);
+            } break;
+
+            case Intent.ACTION_MANAGE_PERMISSION_APPS: {
+                String permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME);
+                if (permissionName == null) {
+                    Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME");
+                    finish();
+                    return;
+                }
+                fragment = PermissionAppsFragment.newInstance(permissionName);
+            } break;
+
+            default: {
+                Log.w(LOG_TAG, "Unrecognized action " + action);
                 finish();
                 return;
             }
-            fragment = AppPermissionsFragment.newInstance(packageName);
-        } else if (Intent.ACTION_MANAGE_PERMISSION_APPS.equals(action)) {
-            String permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME);
-            if (permissionName == null) {
-                Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME");
-                finish();
-                return;
-            }
-            fragment = PermissionAppsFragment.newInstance(permissionName);
-        } else {
-            Log.w(LOG_TAG, "Unrecognized action " + action);
-            finish();
-            return;
         }
 
         getFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit();
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java b/PermissionController/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java
new file mode 100644
index 0000000..5158b5e
--- /dev/null
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/ManagePermissionsFragment.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.permission.ui;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.FragmentTransaction;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.model.PermissionGroup;
+import com.android.packageinstaller.permission.model.PermissionGroups;
+import com.android.packageinstaller.permission.utils.Utils;
+
+import java.util.List;
+
+public final class ManagePermissionsFragment extends PreferenceFragment
+        implements PermissionGroups.PermissionsGroupsChangeCallback, OnPreferenceClickListener {
+    private static final String LOG_TAG = "ManagePermissionsFragment";
+
+    private static final String OS_PKG = "android";
+
+    private PermissionGroups mPermissions;
+
+    private PreferenceScreen mExtraScreen;
+
+    private boolean mShowLegacyPermissions;
+
+    public static ManagePermissionsFragment newInstance() {
+        return new ManagePermissionsFragment();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setHasOptionsMenu(true);
+        final ActionBar ab = getActivity().getActionBar();
+        if (ab != null) {
+            ab.setDisplayHomeAsUpEnabled(true);
+        }
+        mPermissions = new PermissionGroups(getActivity(), getLoaderManager(), this);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mPermissions.refresh();
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.toggle_legacy_permissions, menu);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        MenuItem item = menu.findItem(R.id.toggle_legacy_permissions);
+        if (!mShowLegacyPermissions) {
+            item.setTitle(R.string.show_legacy_permissions);
+        } else {
+            item.setTitle(R.string.hide_legacy_permissions);
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home: {
+                getActivity().finish();
+                return true;
+            }
+
+            case R.id.toggle_legacy_permissions: {
+                mShowLegacyPermissions = !mShowLegacyPermissions;
+                updatePermissionsUi();
+                return true;
+            }
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        String key = preference.getKey();
+
+        PermissionGroup group = mPermissions.getGroup(key);
+        if (group == null) {
+            return false;
+        }
+
+        Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS)
+                .putExtra(Intent.EXTRA_PERMISSION_NAME, key);
+        try {
+            getActivity().startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            Log.w(LOG_TAG, "No app to handle " + intent);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onPermissionGroupsChanged() {
+        updatePermissionsUi();
+    }
+
+    private void updatePermissionsUi() {
+        Activity activity = getActivity();
+
+        if (activity == null) {
+            return;
+        }
+
+        List<PermissionGroup> groups = mPermissions.getGroups();
+
+        PreferenceScreen screen = getPreferenceScreen();
+        if (screen == null) {
+            screen = getPreferenceManager().createPreferenceScreen(activity);
+            setPreferenceScreen(screen);
+        } else {
+            screen.removeAll();
+            if (mExtraScreen != null) {
+                mExtraScreen.removeAll();
+            }
+        }
+
+        for (PermissionGroup group : groups) {
+            // Show legacy permissions only if the user chose that.
+            if (!mShowLegacyPermissions && group.getDeclaringPackage().equals(OS_PKG)
+                    && !Utils.isModernPermissionGroup(group.getName())) {
+                continue;
+            }
+
+            Preference preference = new Preference(activity);
+            preference.setOnPreferenceClickListener(this);
+            preference.setKey(group.getName());
+            preference.setIcon(group.getIcon());
+            preference.setTitle(group.getLabel());
+            preference.setPersistent(false);
+
+            if (group.getDeclaringPackage().equals(OS_PKG)) {
+                screen.addPreference(preference);
+            } else {
+                if (mExtraScreen == null) {
+                    mExtraScreen = getPreferenceManager().createPreferenceScreen(activity);
+                }
+                mExtraScreen.addPreference(preference);
+            }
+        }
+
+        if (mExtraScreen != null && mExtraScreen.getPreferenceCount() > 0) {
+            Preference extraScreenPreference = new Preference(activity);
+            extraScreenPreference.setIcon(R.drawable.ic_toc);
+            extraScreenPreference.setTitle(R.string.additional_permissions);
+            extraScreenPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    AdditionalPermissionsFragment frag = new AdditionalPermissionsFragment();
+                    frag.setTargetFragment(ManagePermissionsFragment.this, 0);
+                    FragmentTransaction ft = getFragmentManager().beginTransaction();
+                    ft.replace(android.R.id.content, frag);
+                    ft.addToBackStack("AdditionalPerms");
+                    ft.commit();
+                    return true;
+                }
+            });
+            extraScreenPreference.setSummary(getString(R.string.additional_permissions_more,
+                    mExtraScreen.getPreferenceCount()));
+            screen.addPreference(extraScreenPreference);
+        }
+    }
+
+    public static class AdditionalPermissionsFragment extends SettingsWithHeader {
+        @Override
+        public void onCreate(Bundle icicle) {
+            super.onCreate(icicle);
+            setPreferenceScreen(((ManagePermissionsFragment) getTargetFragment()).mExtraScreen);
+        }
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            super.onViewCreated(view, savedInstanceState);
+            Resources resources = getResources();
+            Theme theme = getActivity().getTheme();
+            setHeader(resources.getDrawable(R.drawable.ic_toc, theme),
+                    getString(R.string.additional_permissions), null);
+        }
+    }
+}
diff --git a/PermissionController/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java b/PermissionController/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java
index d1d7c87..cbfcc52 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/ui/PermissionAppsFragment.java
@@ -37,10 +37,10 @@
 import android.widget.TextView;
 
 import com.android.packageinstaller.R;
+import com.android.packageinstaller.permission.model.AppPermissionGroup;
 import com.android.packageinstaller.permission.model.PermissionApps;
 import com.android.packageinstaller.permission.model.PermissionApps.Callback;
 import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp;
-import com.android.packageinstaller.permission.model.PermissionGroup;
 import com.android.packageinstaller.permission.utils.SafetyNetLogger;
 
 import java.util.ArrayList;
@@ -59,7 +59,7 @@
 
     private PermissionApps mPermissionApps;
 
-    private ArrayMap<String, PermissionGroup> mToggledGroups;
+    private ArrayMap<String, AppPermissionGroup> mToggledGroups;
     private boolean mHasConfirmedRevoke;
 
     @Override
@@ -136,6 +136,11 @@
     @Override
     public void onPermissionsLoaded() {
         Context context = getActivity();
+
+        if (context == null) {
+            return;
+        }
+
         PreferenceScreen preferences = getPreferenceScreen();
         if (preferences == null) {
             preferences = getPreferenceManager().createPreferenceScreen(getActivity());
@@ -212,7 +217,7 @@
         logToggledGroups();
     }
 
-    private void addToggledGroup(String packageName, PermissionGroup group) {
+    private void addToggledGroup(String packageName, AppPermissionGroup group) {
         if (mToggledGroups == null) {
             mToggledGroups = new ArrayMap<>();
         }
@@ -229,7 +234,7 @@
             final int groupCount = mToggledGroups.size();
             for (int i = 0; i < groupCount; i++) {
                 String packageName = mToggledGroups.keyAt(i);
-                List<PermissionGroup> groups = new ArrayList<>();
+                List<AppPermissionGroup> groups = new ArrayList<>();
                 groups.add(mToggledGroups.valueAt(i));
                 SafetyNetLogger.logPermissionsToggled(packageName, groups);
             }
diff --git a/PermissionController/src/com/android/packageinstaller/permission/utils/SafetyNetLogger.java b/PermissionController/src/com/android/packageinstaller/permission/utils/SafetyNetLogger.java
index 0163b75..8280ba3 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/utils/SafetyNetLogger.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/utils/SafetyNetLogger.java
@@ -18,7 +18,7 @@
 
 import android.content.pm.PackageInfo;
 import android.util.EventLog;
-import com.android.packageinstaller.permission.model.PermissionGroup;
+import com.android.packageinstaller.permission.model.AppPermissionGroup;
 
 import java.util.List;
 
@@ -38,27 +38,27 @@
     }
 
     public static void logPermissionsRequested(PackageInfo packageInfo,
-            List<PermissionGroup> groups) {
+            List<AppPermissionGroup> groups) {
         EventLog.writeEvent(SNET_NET_EVENT_LOG_TAG, PERMISSIONS_REQUESTED,
                 packageInfo.applicationInfo.uid, buildChangedGroupForPackageMessage(
                         packageInfo.packageName, groups));
     }
 
-    public static void logPermissionsToggled(String packageName, List<PermissionGroup> groups) {
+    public static void logPermissionsToggled(String packageName, List<AppPermissionGroup> groups) {
         EventLog.writeEvent(SNET_NET_EVENT_LOG_TAG, PERMISSIONS_TOGGLED,
                 android.os.Process.myUid(), buildChangedGroupForPackageMessage(
                         packageName, groups));
     }
 
     private static String buildChangedGroupForPackageMessage(String packageName,
-            List<PermissionGroup> groups) {
+            List<AppPermissionGroup> groups) {
         StringBuilder builder = new StringBuilder();
 
         builder.append(packageName).append(':');
 
         final int groupCount = groups.size();
         for (int i = 0; i < groupCount; i++) {
-            PermissionGroup group = groups.get(i);
+            AppPermissionGroup group = groups.get(i);
             if (i > 0) {
                 builder.append(';');
             }
diff --git a/PermissionController/src/com/android/packageinstaller/permission/utils/Utils.java b/PermissionController/src/com/android/packageinstaller/permission/utils/Utils.java
index 0dea8f3..7abb262 100644
--- a/PermissionController/src/com/android/packageinstaller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/packageinstaller/permission/utils/Utils.java
@@ -16,6 +16,7 @@
 
 package com.android.packageinstaller.permission.utils;
 
+import android.Manifest;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -36,4 +37,23 @@
             return null;
         }
     }
+
+    public static boolean isModernPermissionGroup(String name) {
+        switch (name) {
+            case Manifest.permission_group.CALENDAR:
+            case Manifest.permission_group.CAMERA:
+            case Manifest.permission_group.CONTACTS:
+            case Manifest.permission_group.LOCATION:
+            case Manifest.permission_group.SENSORS:
+            case Manifest.permission_group.SMS:
+            case Manifest.permission_group.PHONE:
+            case Manifest.permission_group.MICROPHONE: {
+                return true;
+            }
+
+            default: {
+                return false;
+            }
+        }
+    }
 }