[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 2de44cce33 -s ours am: 196b23e356 -s ours am: 1dd4ab4fb9 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Permission/+/16588225

Change-Id: Icea785f06db89833026c396584f027d59afd9534
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml
index ba03cbf..7a08681 100644
--- a/PermissionController/res/values/strings.xml
+++ b/PermissionController/res/values/strings.xml
@@ -136,6 +136,9 @@
     <!-- Label when there are no unused apps [CHAR LIMIT=30] -->
     <string name="no_unused_apps">No unused apps</string>
 
+    <!-- Label when there are zero unused apps [CHAR LIMIT=30] -->
+    <string name="zero_unused_apps">0 unused apps</string>
+
     <!-- [CHAR LIMIT=30] Manage applications, label for option to disable app -->
     <string name="app_disable_dlg_positive">Disable app</string>
 
diff --git a/PermissionController/res/values/themes.xml b/PermissionController/res/values/themes.xml
index 04265a3..16ff3c1 100644
--- a/PermissionController/res/values/themes.xml
+++ b/PermissionController/res/values/themes.xml
@@ -57,6 +57,10 @@
         <item name="android:windowIsTranslucent">true</item>
     </style>
 
+    <style name="GrantPermissions.Car">
+        <item name="carUiActivity">true</item>
+    </style>
+
     <!-- Unused since R but exposed as overlayable. -->
     <style name="Header.Settings"
             parent="@android:style/Theme.DeviceDefault.Settings">
@@ -118,6 +122,10 @@
         <item name="android:filterTouchesWhenObscured">true</item>
     </style>
 
+    <style name="GrantPermissions.Car.FilterTouches">
+        <item name="android:filterTouchesWhenObscured">true</item>
+    </style>
+
     <style name="RequestRole.FilterTouches">
         <item name="android:filterTouchesWhenObscured">true</item>
     </style>
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionUsages.java b/PermissionController/src/com/android/permissioncontroller/permission/model/PermissionUsages.java
similarity index 92%
rename from PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionUsages.java
rename to PermissionController/src/com/android/permissioncontroller/permission/model/PermissionUsages.java
index f83309f..b1dd404 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionUsages.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/PermissionUsages.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.permissioncontroller.permission.ui.handheld.dashboard;
+package com.android.permissioncontroller.permission.model;
 
 import static android.Manifest.permission.CAMERA;
 import static android.Manifest.permission.RECORD_AUDIO;
@@ -45,10 +45,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
-import com.android.permissioncontroller.permission.model.AppPermissionGroup;
-import com.android.permissioncontroller.permission.model.AppPermissionUsage;
 import com.android.permissioncontroller.permission.model.AppPermissionUsage.Builder;
-import com.android.permissioncontroller.permission.model.Permission;
 import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
 import com.android.permissioncontroller.permission.model.legacy.PermissionGroup;
 import com.android.permissioncontroller.permission.model.legacy.PermissionGroups;
@@ -92,14 +89,27 @@
 
     private @Nullable PermissionsUsagesChangeCallback mCallback;
 
+    /**
+     * Callback for when the permission usages has loaded or changed.
+     */
     public interface PermissionsUsagesChangeCallback {
+        /**
+         * Called when the permission usages have loaded or changed.
+         */
         void onPermissionUsagesChanged();
     }
 
+    /**
+     * Creates a new instance of {@link PermissionUsages}.
+     */
     public PermissionUsages(@NonNull Context context) {
         mContext = context;
     }
 
+    /**
+     * Start the {@link Loader} to load the permission usages in the background. Loads without a uid
+     * filter.
+     */
     public void load(@Nullable String filterPackageName,
             @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis,
             long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager,
@@ -110,6 +120,10 @@
                 getNonPlatformPermissions, callback, sync);
     }
 
+    /**
+     * Start the {@link Loader} to load the permission usages in the background. Loads only
+     * permissions for the specified {@code filterUid}.
+     */
     public void load(int filterUid, @Nullable String filterPackageName,
             @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis,
             long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager,
@@ -155,36 +169,20 @@
         mCallback.onPermissionUsagesChanged();
     }
 
+    /**
+     * Return the usages that have already been loaded.
+     */
     public @NonNull List<AppPermissionUsage> getUsages() {
         return mUsages;
     }
 
+    /**
+     * Stop the {@link Loader} from loading the usages.
+     */
     public void stopLoader(@NonNull LoaderManager loaderManager) {
         loaderManager.destroyLoader(1);
     }
 
-    public static @Nullable AppPermissionUsage.GroupUsage loadLastGroupUsage(
-            @NonNull Context context, @NonNull AppPermissionGroup group) {
-        final ArraySet<String> opNames = new ArraySet<>();
-        final List<Permission> permissions = group.getPermissions();
-        final int permCount = permissions.size();
-        for (int i = 0; i < permCount; i++) {
-            final Permission permission = permissions.get(i);
-            final String opName = permission.getAppOp();
-            if (opName != null) {
-                opNames.add(opName);
-            }
-        }
-        final String[] opNamesArray = opNames.toArray(new String[opNames.size()]);
-        final List<PackageOps> usageOps = context.getSystemService(AppOpsManager.class)
-                .getOpsForPackage(group.getApp().applicationInfo.uid,
-                        group.getApp().packageName, opNamesArray);
-        if (usageOps == null || usageOps.isEmpty()) {
-            return null;
-        }
-        return new AppPermissionUsage.GroupUsage(group, usageOps.get(0), null);
-    }
-
     private static final class UsageLoader extends AsyncTaskLoader<List<AppPermissionUsage>> {
         private final int mFilterUid;
         private @Nullable String mFilterPackageName;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index b96efef..812e4fe 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -127,6 +127,9 @@
 
     @Override
     public void onCreate(Bundle icicle) {
+        if (DeviceUtils.isAuto(this)) {
+            setTheme(R.style.GrantPermissions_Car_FilterTouches);
+        }
         super.onCreate(icicle);
 
         if (icicle == null) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index bf48e28..f8594ac 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -256,10 +256,10 @@
                 if (DeviceUtils.isAuto(this)) {
                     if (allPermissions) {
                         androidXFragment = AutoAllAppPermissionsFragment.newInstance(packageName,
-                                userHandle);
+                                userHandle, sessionId);
                     } else {
                         androidXFragment = AutoAppPermissionsFragment.newInstance(packageName,
-                                userHandle);
+                                userHandle, sessionId, /* isSystemPermsScreen= */ true);
                     }
                 } else if (DeviceUtils.isWear(this)) {
                     androidXFragment = AppPermissionsFragmentWear.newInstance(packageName);
@@ -268,7 +268,7 @@
                             .AppPermissionsFragment.newInstance(packageName, userHandle);
                 } else {
                     Bundle args = AppPermissionGroupsFragment.createArgs(packageName, userHandle,
-                            sessionId, true);
+                            sessionId, /* isSystemPermsScreen= */  true);
                     setNavGraph(args, R.id.app_permission_groups);
                     return;
                 }
@@ -296,7 +296,8 @@
                     return;
                 }
                 if (DeviceUtils.isAuto(this)) {
-                    androidXFragment = AutoPermissionAppsFragment.newInstance(permissionName);
+                    androidXFragment =
+                            AutoPermissionAppsFragment.newInstance(permissionName, sessionId);
                 } else if (DeviceUtils.isTelevision(this)) {
                     androidXFragment = com.android.permissioncontroller.permission.ui.television
                             .PermissionAppsFragment.newInstance(permissionName);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt
index c1d26dd..73aeb76 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt
@@ -26,6 +26,7 @@
 import android.os.UserHandle
 import android.util.Log
 import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.Fragment
 import androidx.lifecycle.Observer
 import androidx.lifecycle.ViewModelProvider
 import androidx.preference.Preference
@@ -52,11 +53,9 @@
  * A fragment displaying all applications that are unused as well as the option to remove them
  * and to open them.
  */
-class UnusedAppsFragment<PF, UnusedAppPref> : PreferenceFragmentCompat()
+class UnusedAppsFragment<PF, UnusedAppPref> : Fragment()
     where PF : PreferenceFragmentCompat, PF : UnusedAppsFragment.Parent<UnusedAppPref>,
-          UnusedAppPref : Preference, UnusedAppPref : RemovablePref {
-
-    private val INFO_MSG_CATEGORY = "info_msg_category"
+        UnusedAppPref : Preference, UnusedAppPref : RemovablePref {
 
     private lateinit var viewModel: UnusedAppsViewModel
     private lateinit var collator: Collator
@@ -64,6 +63,7 @@
     private var isFirstLoad = false
 
     companion object {
+        public const val INFO_MSG_CATEGORY = "info_msg_category"
         private const val SHOW_LOAD_DELAY_MS = 200L
         private const val INFO_MSG_KEY = "info_msg"
         private const val ELEVATION_HIGH = 8f
@@ -91,10 +91,6 @@
         }
     }
 
-    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
-        // empty
-    }
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         val preferenceFragment: PF = requirePreferenceFragment()
@@ -153,14 +149,15 @@
      */
     private fun createPreferenceScreen() {
         val preferenceFragment: PF = requirePreferenceFragment()
-        val preferenceScreen = preferenceManager.inflateFromResource(
+        val preferenceScreen = preferenceFragment.preferenceManager.inflateFromResource(
             context,
             R.xml.unused_app_categories,
             /* rootPreferences= */ null)
         preferenceFragment.preferenceScreen = preferenceScreen
 
         val infoMsgCategory = preferenceScreen.findPreference<PreferenceCategory>(INFO_MSG_CATEGORY)
-        val footerPreference = preferenceFragment.createFooterPreference(context!!)
+        val footerPreference = preferenceFragment.createFooterPreference(
+                preferenceFragment.preferenceManager.context)
         footerPreference.key = INFO_MSG_KEY
         infoMsgCategory?.addPreference(footerPreference)
     }
@@ -211,7 +208,8 @@
                 var pref = category.findPreference<UnusedAppPref>(key)
                 if (pref == null) {
                     pref = removedPrefs[key] ?: preferenceFragment.createUnusedAppPref(
-                        activity!!.application, pkgName, user, preferenceManager.context!!)
+                        activity!!.application, pkgName, user,
+                        preferenceFragment.preferenceManager.context)
                     pref.key = key
                     pref.title = KotlinUtils.getPackageLabel(activity!!.application, pkgName, user)
                 }
@@ -253,9 +251,7 @@
             }
         }
 
-        val infoMsgCategory =
-            preferenceScreen.findPreference<PreferenceCategory>(INFO_MSG_CATEGORY)!!
-        infoMsgCategory.isVisible = !allCategoriesEmpty
+        preferenceFragment.setEmptyState(allCategoriesEmpty)
 
         if (isFirstLoad) {
             if (categorizedPackages[Months.SIX]!!.isNotEmpty() ||
@@ -377,5 +373,12 @@
             user: UserHandle,
             context: Context
         ): UnusedAppPref
+
+        /**
+         * Updates the state based on whether the content is empty.
+         *
+         * @param empty whether the content is empty
+         */
+        fun setEmptyState(empty: Boolean)
     }
 }
\ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAllAppPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAllAppPermissionsFragment.java
index f065f1c..e509d8a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAllAppPermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAllAppPermissionsFragment.java
@@ -16,6 +16,8 @@
 
 package com.android.permissioncontroller.permission.ui.auto;
 
+import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -62,18 +64,19 @@
 
     /** Creates an {@link AutoAllAppPermissionsFragment} with no filter. */
     public static AutoAllAppPermissionsFragment newInstance(@NonNull String packageName,
-            @NonNull UserHandle userHandle) {
-        return newInstance(packageName, /* filterGroup= */ null, userHandle);
+            @NonNull UserHandle userHandle, long sessionId) {
+        return newInstance(packageName, /* filterGroup= */ null, userHandle, sessionId);
     }
 
     /** Creates an {@link AutoAllAppPermissionsFragment} with a specific filter group. */
     public static AutoAllAppPermissionsFragment newInstance(@NonNull String packageName,
-            @NonNull String filterGroup, @NonNull UserHandle userHandle) {
+            @NonNull String filterGroup, @NonNull UserHandle userHandle, long sessionId) {
         AutoAllAppPermissionsFragment instance = new AutoAllAppPermissionsFragment();
         Bundle arguments = new Bundle();
         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
         arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, filterGroup);
         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
+        arguments.putLong(EXTRA_SESSION_ID, sessionId);
         instance.setArguments(arguments);
         return instance;
     }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
index 3181db4..22b9c41 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java
@@ -16,31 +16,35 @@
 
 package com.android.permissioncontroller.permission.ui.auto;
 
+import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
+import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY;
 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED;
 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 import android.app.Activity;
 import android.app.Dialog;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.text.BidiFormatter;
 import android.util.Log;
 import android.view.View;
 import android.widget.RadioButton;
 
-import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.content.res.TypedArrayUtils;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
 import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
@@ -50,35 +54,26 @@
 import com.android.car.ui.AlertDialogBuilder;
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
-import com.android.permissioncontroller.permission.model.AppPermissionGroup;
-import com.android.permissioncontroller.permission.model.Permission;
 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler;
-import com.android.permissioncontroller.permission.utils.LocationUtils;
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel;
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
 import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor;
-import com.android.permissioncontroller.permission.utils.SafetyNetLogger;
-import com.android.permissioncontroller.permission.utils.Utils;
 import com.android.settingslib.RestrictedLockUtils;
 
-import java.lang.annotation.Retention;
-import java.util.List;
+import java.util.Map;
+
+import kotlin.Pair;
 
 /** Settings related to a particular permission for the given app. */
-public class AutoAppPermissionFragment extends AutoSettingsFrameFragment {
+public class AutoAppPermissionFragment extends AutoSettingsFrameFragment
+        implements AppPermissionViewModel.ConfirmDialogShowingFragment {
 
     private static final String LOG_TAG = "AppPermissionFragment";
-
-    @Retention(SOURCE)
-    @IntDef(value = {CHANGE_FOREGROUND, CHANGE_BACKGROUND}, flag = true)
-    @interface ChangeTarget {
-    }
-
-    static final int CHANGE_FOREGROUND = 1;
-    static final int CHANGE_BACKGROUND = 2;
-    static final int CHANGE_BOTH = CHANGE_FOREGROUND | CHANGE_BACKGROUND;
+    private static final long POST_DELAY_MS = 20;
 
     @NonNull
-    private AppPermissionGroup mGroup;
-
+    private TwoStatePreference mAllowPermissionPreference;
     @NonNull
     private TwoStatePreference mAlwaysPermissionPreference;
     @NonNull
@@ -88,14 +83,19 @@
     @NonNull
     private AutoTwoTargetPreference mDetailsPreference;
 
-    private boolean mHasConfirmedRevoke;
-
-    /**
-     * Listens for changes to the permission of the app the permission is currently getting
-     * granted to. {@code null} when unregistered.
-     */
-    @Nullable
-    private PackageManager.OnPermissionsChangedListener mPermissionChangeListener;
+    @NonNull
+    private AppPermissionViewModel mViewModel;
+    @NonNull
+    private String mPackageName;
+    @NonNull
+    private String mPermGroupName;
+    @NonNull
+    private UserHandle mUser;
+    @NonNull
+    private String mPackageLabel;
+    @NonNull
+    private String mPermGroupLabel;
+    private Drawable mPackageIcon;
 
     /**
      * Listens for changes to the app the permission is currently getting granted to. {@code null}
@@ -114,7 +114,8 @@
      */
     @NonNull
     public static AutoAppPermissionFragment newInstance(@NonNull String packageName,
-            @NonNull String permName, @Nullable String groupName, @NonNull UserHandle userHandle) {
+            @NonNull String permName, @Nullable String groupName, @NonNull UserHandle userHandle,
+            @NonNull long sessionId) {
         AutoAppPermissionFragment fragment = new AutoAppPermissionFragment();
         Bundle arguments = new Bundle();
         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
@@ -124,75 +125,34 @@
             arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName);
         }
         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
+        arguments.putLong(EXTRA_SESSION_ID, sessionId);
         fragment.setArguments(arguments);
         return fragment;
     }
 
     @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mHasConfirmedRevoke = false;
-
-        mGroup = getAppPermissionGroup();
-        if (mGroup == null) {
-            requireActivity().setResult(Activity.RESULT_CANCELED);
-            requireActivity().finish();
-            return;
-        }
-
-        setHeaderLabel(
-                requireContext().getString(R.string.app_permission_title, mGroup.getFullLabel()));
-    }
-
-    private void setResult(@GrantPermissionsViewHandler.Result int result) {
-        Intent intent = new Intent()
-                .putExtra(EXTRA_RESULT_PERMISSION_INTERACTED,
-                        requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME))
-                .putExtra(EXTRA_RESULT_PERMISSION_RESULT, result);
-        requireActivity().setResult(Activity.RESULT_OK, intent);
-    }
-
-    private AppPermissionGroup getAppPermissionGroup() {
-        Activity activity = requireActivity();
-        Context context = getPreferenceManager().getContext();
-
-        String packageName = requireArguments().getString(Intent.EXTRA_PACKAGE_NAME);
-        String groupName = requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
-        if (groupName == null) {
-            groupName = requireArguments().getString(Intent.EXTRA_PERMISSION_NAME);
-        }
-        PackageItemInfo groupInfo = Utils.getGroupInfo(groupName, context);
-        List<PermissionInfo> groupPermInfos = Utils.getGroupPermissionInfos(groupName, context);
-        if (groupInfo == null || groupPermInfos == null) {
-            Log.i(LOG_TAG, "Illegal group: " + groupName);
-            return null;
-        }
-        UserHandle userHandle = requireArguments().getParcelable(Intent.EXTRA_USER);
-        if (userHandle == null) {
-            Log.e(LOG_TAG, "User handle is null");
-            return null;
-        }
-        PackageInfo packageInfo = AutoPermissionsUtils.getPackageInfo(activity, packageName,
-                userHandle);
-        if (packageInfo == null) {
-            Log.i(LOG_TAG, "PackageInfo is null");
-            return null;
-        }
-        AppPermissionGroup group = AppPermissionGroup.create(context, packageInfo, groupInfo,
-                groupPermInfos, false);
-
-        if (group == null || !Utils.shouldShowPermission(context, group)) {
-            Log.i(LOG_TAG, "Illegal group: " + (group == null ? "null" : group.getName()));
-            return null;
-        }
-
-        return group;
+    public void onCreatePreferences(Bundle bundle, String s) {
+        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(requireContext()));
     }
 
     @Override
-    public void onCreatePreferences(Bundle bundle, String s) {
-        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(requireContext()));
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
+        mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
+        if (mPermGroupName == null) {
+            mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
+        }
+        mUser = getArguments().getParcelable(Intent.EXTRA_USER);
+        mPackageLabel = BidiFormatter.getInstance().unicodeWrap(
+                KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(), mPackageName,
+                        mUser));
+        mPermGroupLabel = KotlinUtils.INSTANCE.getPermGroupLabel(getContext(),
+                mPermGroupName).toString();
+        mPackageIcon = KotlinUtils.INSTANCE.getBadgedPackageIcon(getActivity().getApplication(),
+                mPackageName, mUser);
+        setHeaderLabel(
+                requireContext().getString(R.string.app_permission_title, mPermGroupLabel));
     }
 
     @Override
@@ -202,14 +162,18 @@
         PreferenceScreen screen = getPreferenceScreen();
         screen.addPreference(
                 AutoPermissionsUtils.createHeaderPreference(requireContext(),
-                        mGroup.getApp().applicationInfo));
+                        mPackageIcon, mPackageName, mPackageLabel));
 
         // Add permissions selector preferences.
         PreferenceGroup permissionSelector = new PreferenceCategory(requireContext());
         permissionSelector.setTitle(
-                getString(R.string.app_permission_header, mGroup.getFullLabel()));
+                getString(R.string.app_permission_header, mPermGroupLabel));
         screen.addPreference(permissionSelector);
 
+        mAllowPermissionPreference = new SelectedPermissionPreference(requireContext());
+        mAllowPermissionPreference.setTitle(R.string.app_permission_button_allow);
+        permissionSelector.addPreference(mAllowPermissionPreference);
+
         mAlwaysPermissionPreference = new SelectedPermissionPreference(requireContext());
         mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow_always);
         permissionSelector.addPreference(mAlwaysPermissionPreference);
@@ -223,6 +187,35 @@
         mDenyPermissionPreference.setTitle(R.string.app_permission_button_deny);
         permissionSelector.addPreference(mDenyPermissionPreference);
 
+        mAllowPermissionPreference.setOnPreferenceClickListener(v -> {
+            checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType.ALLOW);
+            setResult(GrantPermissionsViewHandler.GRANTED_ALWAYS);
+            requestChange(AppPermissionViewModel.ChangeRequest.GRANT_FOREGROUND,
+                    APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW);
+            return true;
+        });
+        mAlwaysPermissionPreference.setOnPreferenceClickListener(v -> {
+            checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType.ALLOW_ALWAYS);
+            setResult(GrantPermissionsViewHandler.GRANTED_ALWAYS);
+            requestChange(AppPermissionViewModel.ChangeRequest.GRANT_BOTH,
+                    APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS);
+            return true;
+        });
+        mForegroundOnlyPermissionPreference.setOnPreferenceClickListener(v -> {
+            checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND);
+            setResult(GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY);
+            requestChange(AppPermissionViewModel.ChangeRequest.GRANT_FOREGROUND_ONLY,
+                    APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND);
+            return true;
+        });
+        mDenyPermissionPreference.setOnPreferenceClickListener(v -> {
+            checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType.DENY);
+            setResult(GrantPermissionsViewHandler.DENIED);
+            requestChange(AppPermissionViewModel.ChangeRequest.REVOKE_BOTH,
+                    APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY);
+            return true;
+        });
+
         mDetailsPreference = new AutoTwoTargetPreference(requireContext());
         screen.addPreference(mDetailsPreference);
     }
@@ -232,17 +225,11 @@
         super.onStart();
         Activity activity = requireActivity();
 
-        mPermissionChangeListener = new PermissionChangeListener(
-                mGroup.getApp().applicationInfo.uid);
-        PackageManager pm = activity.getPackageManager();
-        pm.addOnPermissionsChangeListener(mPermissionChangeListener);
-
         // Get notified when the package is removed.
-        String packageName = mGroup.getApp().packageName;
-        mPackageRemovalMonitor = new PackageRemovalMonitor(requireContext(), packageName) {
+        mPackageRemovalMonitor = new PackageRemovalMonitor(requireContext(), mPackageName) {
             @Override
             public void onPackageRemoved() {
-                Log.w(LOG_TAG, packageName + " was uninstalled");
+                Log.w(LOG_TAG, mPackageName + " was uninstalled");
                 activity.setResult(Activity.RESULT_CANCELED);
                 activity.finish();
             }
@@ -251,18 +238,22 @@
 
         // Check if the package was removed while this activity was not started.
         try {
-            activity.createPackageContextAsUser(packageName, /* flags= */ 0,
-                    mGroup.getUser()).getPackageManager().getPackageInfo(packageName,
+            activity.createPackageContextAsUser(mPackageName, /* flags= */ 0,
+                    mUser).getPackageManager().getPackageInfo(mPackageName,
                     /* flags= */ 0);
         } catch (PackageManager.NameNotFoundException e) {
-            Log.w(LOG_TAG, packageName + " was uninstalled while this activity was stopped", e);
+            Log.w(LOG_TAG, mPackageName + " was uninstalled while this activity was stopped", e);
             activity.setResult(Activity.RESULT_CANCELED);
             activity.finish();
         }
 
-        // Re-create the permission group in case permissions have changed and update the UI.
-        mGroup = getAppPermissionGroup();
-        updateUi();
+        long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
+        AppPermissionViewModelFactory factory = new AppPermissionViewModelFactory(
+                getActivity().getApplication(), mPackageName, mPermGroupName, mUser, sessionId);
+        mViewModel = new ViewModelProvider(this, factory).get(AppPermissionViewModel.class);
+        mViewModel.getButtonStateLiveData().observe(this, this::setRadioButtonsState);
+        mViewModel.getDetailResIdLiveData().observe(this, this::setDetail);
+        mViewModel.getShowAdminSupportLiveData().observe(this, this::setAdminSupportDetail);
     }
 
     @Override
@@ -273,453 +264,16 @@
             mPackageRemovalMonitor.unregister();
             mPackageRemovalMonitor = null;
         }
-
-        if (mPermissionChangeListener != null) {
-            requireActivity().getPackageManager().removeOnPermissionsChangeListener(
-                    mPermissionChangeListener);
-            mPermissionChangeListener = null;
-        }
     }
 
-    private void updateUi() {
-        mDetailsPreference.setOnSecondTargetClickListener(null);
-        mDetailsPreference.setVisible(false);
-
-        if (mGroup.areRuntimePermissionsGranted()) {
-            if (!mGroup.hasPermissionWithBackgroundMode()
-                    || (mGroup.getBackgroundPermissions() != null
-                    && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted())) {
-                setSelectedPermissionState(mAlwaysPermissionPreference);
-            } else {
-                setSelectedPermissionState(mForegroundOnlyPermissionPreference);
-            }
-        } else {
-            setSelectedPermissionState(mDenyPermissionPreference);
-        }
-
-        mAlwaysPermissionPreference.setOnPreferenceClickListener(v -> {
-            setResult(GrantPermissionsViewHandler.GRANTED_ALWAYS);
-            return requestChange(/* requestGrant= */true, CHANGE_BOTH);
-        });
-        mForegroundOnlyPermissionPreference.setOnPreferenceClickListener(v -> {
-            setResult(GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY);
-            requestChange(/* requestGrant= */false, CHANGE_BACKGROUND);
-            requestChange(/* requestGrant= */true, CHANGE_FOREGROUND);
-            return true;
-        });
-        mDenyPermissionPreference.setOnPreferenceClickListener(v -> {
-            setResult(GrantPermissionsViewHandler.DENIED);
-            return requestChange(/* requestGrant= */ false, CHANGE_BOTH);
-        });
-
-        // Set the allow and foreground-only button states appropriately.
-        if (mGroup.hasPermissionWithBackgroundMode()) {
-            if (mGroup.getBackgroundPermissions() == null) {
-                mAlwaysPermissionPreference.setVisible(false);
-            } else {
-                mForegroundOnlyPermissionPreference.setVisible(true);
-                mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow_always);
-            }
-        } else {
-            mForegroundOnlyPermissionPreference.setVisible(false);
-            mAlwaysPermissionPreference.setTitle(R.string.app_permission_button_allow);
-        }
-
-        // Handle the UI for various special cases.
-        if (isSystemFixed() || isPolicyFullyFixed() || isForegroundDisabledByPolicy()) {
-            // Disable changing permissions and potentially show administrator message.
-            mAlwaysPermissionPreference.setEnabled(false);
-            mForegroundOnlyPermissionPreference.setEnabled(false);
-            mDenyPermissionPreference.setEnabled(false);
-
-            RestrictedLockUtils.EnforcedAdmin admin = getAdmin();
-            if (admin != null) {
-                mDetailsPreference.setWidgetLayoutResource(R.layout.info_preference_widget);
-                mDetailsPreference.setOnSecondTargetClickListener(
-                        preference -> RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
-                                getContext(), admin));
-            }
-
-            updateDetailForFixedByPolicyPermissionGroup();
-        } else if (Utils.areGroupPermissionsIndividuallyControlled(requireContext(),
-                mGroup.getName())) {
-            // If the permissions are individually controlled, also show a link to the page that
-            // lets you control them.
-            mDetailsPreference.setWidgetLayoutResource(R.layout.settings_preference_widget);
-            mDetailsPreference.setOnSecondTargetClickListener(
-                    preference -> showAllPermissions(mGroup.getName()));
-
-            updateDetailForIndividuallyControlledPermissionGroup();
-        } else {
-            if (mGroup.hasPermissionWithBackgroundMode()) {
-                if (mGroup.getBackgroundPermissions() == null) {
-                    // The group has background permissions but the app did not request any. I.e.
-                    // The app can only switch between 'never" and "only in foreground".
-                    mAlwaysPermissionPreference.setEnabled(false);
-
-                    mDenyPermissionPreference.setOnPreferenceClickListener(v -> requestChange(false,
-                            CHANGE_FOREGROUND));
-                } else {
-                    if (isBackgroundPolicyFixed()) {
-                        // If background policy is fixed, we only allow switching the foreground.
-                        // Note that this assumes that the background policy is fixed to deny,
-                        // since if it is fixed to grant, so is the foreground.
-                        mAlwaysPermissionPreference.setEnabled(false);
-                        setSelectedPermissionState(mForegroundOnlyPermissionPreference);
-
-                        mDenyPermissionPreference.setOnPreferenceClickListener(
-                                v -> requestChange(false, CHANGE_FOREGROUND));
-
-                        updateDetailForFixedByPolicyPermissionGroup();
-                    } else if (isForegroundPolicyFixed()) {
-                        // Foreground permissions are fixed to allow (the first case above handles
-                        // fixing to deny), so we only allow toggling background permissions.
-                        mDenyPermissionPreference.setEnabled(false);
-
-                        mAlwaysPermissionPreference.setOnPreferenceClickListener(
-                                v -> requestChange(true, CHANGE_BACKGROUND));
-                        mForegroundOnlyPermissionPreference.setOnPreferenceClickListener(
-                                v -> requestChange(false, CHANGE_BACKGROUND));
-
-                        updateDetailForFixedByPolicyPermissionGroup();
-                    } else {
-                        // The default tri-state case is handled by default.
-                    }
-                }
-
-            } else {
-                // The default bi-state case is handled by default.
-            }
-        }
-    }
-
-    /**
-     * Set the given permission state as the only checked permission state.
-     */
-    private void setSelectedPermissionState(@NonNull TwoStatePreference permissionState) {
-        permissionState.setChecked(true);
-        if (permissionState != mAlwaysPermissionPreference) {
-            mAlwaysPermissionPreference.setChecked(false);
-        }
-        if (permissionState != mForegroundOnlyPermissionPreference) {
-            mForegroundOnlyPermissionPreference.setChecked(false);
-        }
-        if (permissionState != mDenyPermissionPreference) {
-            mDenyPermissionPreference.setChecked(false);
-        }
-    }
-
-    /**
-     * Are any permissions of this group fixed by the system, i.e. not changeable by the user.
-     *
-     * @return {@code true} iff any permission is fixed
-     */
-    private boolean isSystemFixed() {
-        return mGroup.isSystemFixed();
-    }
-
-    /**
-     * Is any foreground permissions of this group fixed by the policy, i.e. not changeable by the
-     * user.
-     *
-     * @return {@code true} iff any foreground permission is fixed
-     */
-    private boolean isForegroundPolicyFixed() {
-        return mGroup.isPolicyFixed();
-    }
-
-    /**
-     * Is any background permissions of this group fixed by the policy, i.e. not changeable by the
-     * user.
-     *
-     * @return {@code true} iff any background permission is fixed
-     */
-    private boolean isBackgroundPolicyFixed() {
-        return mGroup.getBackgroundPermissions() != null
-                && mGroup.getBackgroundPermissions().isPolicyFixed();
-    }
-
-    /**
-     * Are there permissions fixed, so that the user cannot change the preference at all?
-     *
-     * @return {@code true} iff the permissions of this group are fixed
-     */
-    private boolean isPolicyFullyFixed() {
-        return isForegroundPolicyFixed() && (mGroup.getBackgroundPermissions() == null
-                || isBackgroundPolicyFixed());
-    }
-
-    /**
-     * Is the foreground part of this group disabled. If the foreground is disabled, there is no
-     * need to possible grant background access.
-     *
-     * @return {@code true} iff the permissions of this group are fixed
-     */
-    private boolean isForegroundDisabledByPolicy() {
-        return isForegroundPolicyFixed() && !mGroup.areRuntimePermissionsGranted();
-    }
-
-    /**
-     * Get the app that acts as admin for this profile.
-     *
-     * @return The admin or {@code null} if there is no admin.
-     */
-    @Nullable
-    private RestrictedLockUtils.EnforcedAdmin getAdmin() {
-        return RestrictedLockUtils.getProfileOrDeviceOwner(getContext(), mGroup.getUser());
-    }
-
-    /**
-     * Update the detail in the case the permission group has individually controlled permissions.
-     */
-    private void updateDetailForIndividuallyControlledPermissionGroup() {
-        int revokedCount = 0;
-        List<Permission> permissions = mGroup.getPermissions();
-        for (Permission permission : permissions) {
-            if (!permission.isGrantedIncludingAppOp()) {
-                revokedCount++;
-            }
-        }
-
-        int resId;
-        if (revokedCount == 0) {
-            resId = R.string.permission_revoked_none;
-        } else if (revokedCount == permissions.size()) {
-            resId = R.string.permission_revoked_all;
-        } else {
-            resId = R.string.permission_revoked_count;
-        }
-
-        mDetailsPreference.setSummary(getString(resId, revokedCount));
-        mDetailsPreference.setVisible(true);
-    }
-
-    /**
-     * Update the detail of a permission group that is at least partially fixed by policy.
-     */
-    private void updateDetailForFixedByPolicyPermissionGroup() {
-        RestrictedLockUtils.EnforcedAdmin admin = getAdmin();
-        AppPermissionGroup backgroundGroup = mGroup.getBackgroundPermissions();
-
-        boolean hasAdmin = admin != null;
-
-        if (isSystemFixed()) {
-            // Permission is fully controlled by the system and cannot be switched
-
-            setDetail(R.string.permission_summary_enabled_system_fixed);
-        } else if (isForegroundDisabledByPolicy()) {
-            // Permission is fully controlled by policy and cannot be switched
-
-            if (hasAdmin) {
-                setDetail(R.string.disabled_by_admin);
-            } else {
-                // Disabled state will be displayed by switch, so no need to add text for that
-                setDetail(R.string.permission_summary_enforced_by_policy);
-            }
-        } else if (isPolicyFullyFixed()) {
-            // Permission is fully controlled by policy and cannot be switched
-
-            if (backgroundGroup == null) {
-                if (hasAdmin) {
-                    setDetail(R.string.enabled_by_admin);
-                } else {
-                    // Enabled state will be displayed by switch, so no need to add text for
-                    // that
-                    setDetail(R.string.permission_summary_enforced_by_policy);
-                }
-            } else {
-                if (backgroundGroup.areRuntimePermissionsGranted()) {
-                    if (hasAdmin) {
-                        setDetail(R.string.enabled_by_admin);
-                    } else {
-                        // Enabled state will be displayed by switch, so no need to add text for
-                        // that
-                        setDetail(R.string.permission_summary_enforced_by_policy);
-                    }
-                } else {
-                    if (hasAdmin) {
-                        setDetail(
-                                R.string.permission_summary_enabled_by_admin_foreground_only);
-                    } else {
-                        setDetail(
-                                R.string.permission_summary_enabled_by_policy_foreground_only);
-                    }
-                }
-            }
-        } else {
-            // Part of the permission group can still be switched
-
-            if (isBackgroundPolicyFixed()) {
-                if (backgroundGroup.areRuntimePermissionsGranted()) {
-                    if (hasAdmin) {
-                        setDetail(R.string.permission_summary_enabled_by_admin_background_only);
-                    } else {
-                        setDetail(R.string.permission_summary_enabled_by_policy_background_only);
-                    }
-                } else {
-                    if (hasAdmin) {
-                        setDetail(R.string.permission_summary_disabled_by_admin_background_only);
-                    } else {
-                        setDetail(R.string.permission_summary_disabled_by_policy_background_only);
-                    }
-                }
-            } else if (isForegroundPolicyFixed()) {
-                if (hasAdmin) {
-                    setDetail(R.string.permission_summary_enabled_by_admin_foreground_only);
-                } else {
-                    setDetail(R.string.permission_summary_enabled_by_policy_foreground_only);
-                }
-            }
-        }
-    }
-
-    /**
-     * Show the given string as informative text below permission picker preferences.
-     *
-     * @param strId the resourceId of the string to display.
-     */
-    private void setDetail(int strId) {
-        mDetailsPreference.setSummary(strId);
-        mDetailsPreference.setVisible(true);
-    }
-
-    /**
-     * Show all individual permissions in this group in a new fragment.
-     */
-    private void showAllPermissions(@NonNull String filterGroup) {
-        Fragment frag = AutoAllAppPermissionsFragment.newInstance(mGroup.getApp().packageName,
-                filterGroup, UserHandle.getUserHandleForUid(mGroup.getApp().applicationInfo.uid));
-        requireFragmentManager().beginTransaction()
-                .replace(android.R.id.content, frag)
-                .addToBackStack("AllPerms")
-                .commit();
-    }
-
-    /**
-     * Request to grant/revoke permissions group.
-     *
-     * <p>Does <u>not</u> handle:
-     * <ul>
-     * <li>Individually granted permissions</li>
-     * <li>Permission groups with background permissions</li>
-     * </ul>
-     * <p><u>Does</u> handle:
-     * <ul>
-     * <li>Default grant permissions</li>
-     * </ul>
-     *
-     * @param requestGrant If this group should be granted
-     * @param changeTarget Which permission group (foreground/background/both) should be changed
-     * @return If the request was processed.
-     */
-    private boolean requestChange(boolean requestGrant, @ChangeTarget int changeTarget) {
-        if (LocationUtils.isLocationGroupAndProvider(getContext(), mGroup.getName(),
-                mGroup.getApp().packageName)) {
-            LocationUtils.showLocationDialog(getContext(),
-                    Utils.getAppLabel(mGroup.getApp().applicationInfo, requireContext()));
-
-            // The request was denied, so update the buttons.
-            updateUi();
-            return false;
-        }
-
-        if (requestGrant) {
-            if ((changeTarget & CHANGE_FOREGROUND) != 0) {
-                if (!mGroup.areRuntimePermissionsGranted()) {
-                    SafetyNetLogger.logPermissionToggled(mGroup);
-                }
-
-                mGroup.grantRuntimePermissions(true, false);
-            }
-            if ((changeTarget & CHANGE_BACKGROUND) != 0) {
-                if (mGroup.getBackgroundPermissions() != null) {
-                    if (!mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
-                        SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
-                    }
-
-                    mGroup.getBackgroundPermissions().grantRuntimePermissions(true, false);
-                }
-            }
-        } else {
-            boolean showDefaultDenyDialog = false;
-
-            if ((changeTarget & CHANGE_FOREGROUND) != 0
-                    && mGroup.areRuntimePermissionsGranted()) {
-                showDefaultDenyDialog = mGroup.hasGrantedByDefaultPermission()
-                        || !mGroup.doesSupportRuntimePermissions()
-                        || mGroup.hasInstallToRuntimeSplit();
-            }
-            if ((changeTarget & CHANGE_BACKGROUND) != 0) {
-                if (mGroup.getBackgroundPermissions() != null
-                        && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
-                    AppPermissionGroup bgPerm = mGroup.getBackgroundPermissions();
-                    showDefaultDenyDialog |= bgPerm.hasGrantedByDefaultPermission()
-                            || !bgPerm.doesSupportRuntimePermissions()
-                            || bgPerm.hasInstallToRuntimeSplit();
-                }
-            }
-
-            if (showDefaultDenyDialog && !mHasConfirmedRevoke) {
-                showDefaultDenyDialog(changeTarget);
-                updateUi();
-                return false;
-            } else {
-                if ((changeTarget & CHANGE_FOREGROUND) != 0
-                        && mGroup.areRuntimePermissionsGranted()) {
-                    if (mGroup.areRuntimePermissionsGranted()) {
-                        SafetyNetLogger.logPermissionToggled(mGroup);
-                    }
-
-                    mGroup.revokeRuntimePermissions(false);
-                }
-                if ((changeTarget & CHANGE_BACKGROUND) != 0) {
-                    if (mGroup.getBackgroundPermissions() != null
-                            && mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
-                        if (mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
-                            SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
-                        }
-
-                        mGroup.getBackgroundPermissions().revokeRuntimePermissions(false);
-                    }
-                }
-            }
-        }
-
-        updateUi();
-
-        return true;
-    }
-
-    /**
-     * Show a dialog that warns the user that she/he is about to revoke permissions that were
-     * granted by default.
-     *
-     * <p>The order of operation to revoke a permission granted by default is:
-     * <ol>
-     * <li>{@code showDefaultDenyDialog}</li>
-     * <li>{@link DefaultDenyDialog#onCreateDialog}</li>
-     * <li>{@link AutoAppPermissionFragment#onDenyAnyWay}</li>
-     * </ol>
-     *
-     * @param changeTarget Whether background or foreground should be changed
-     */
-    private void showDefaultDenyDialog(@ChangeTarget int changeTarget) {
+    @Override
+    public void showConfirmDialog(AppPermissionViewModel.ChangeRequest changeRequest, int messageId,
+            int buttonPressed, boolean oneTime) {
         Bundle args = new Bundle();
 
-        boolean showGrantedByDefaultWarning = false;
-        if ((changeTarget & CHANGE_FOREGROUND) != 0) {
-            showGrantedByDefaultWarning = mGroup.hasGrantedByDefaultPermission();
-        }
-        if ((changeTarget & CHANGE_BACKGROUND) != 0) {
-            if (mGroup.getBackgroundPermissions() != null) {
-                showGrantedByDefaultWarning |=
-                        mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission();
-            }
-        }
-
-        args.putInt(DefaultDenyDialog.MSG, showGrantedByDefaultWarning ? R.string.system_warning
-                : R.string.old_sdk_deny_warning);
-        args.putInt(DefaultDenyDialog.CHANGE_TARGET, changeTarget);
+        args.putInt(DefaultDenyDialog.MSG, messageId);
+        args.putSerializable(DefaultDenyDialog.CHANGE_REQUEST, changeRequest);
+        args.putSerializable(DefaultDenyDialog.BUTTON, buttonPressed);
 
         DefaultDenyDialog defaultDenyDialog = new DefaultDenyDialog();
         defaultDenyDialog.setArguments(args);
@@ -728,39 +282,97 @@
                 DefaultDenyDialog.class.getName());
     }
 
+    private void setResult(@GrantPermissionsViewHandler.Result int result) {
+        Intent intent = new Intent()
+                .putExtra(EXTRA_RESULT_PERMISSION_INTERACTED,
+                        requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME))
+                .putExtra(EXTRA_RESULT_PERMISSION_RESULT, result);
+        requireActivity().setResult(Activity.RESULT_OK, intent);
+    }
+
+    private void setRadioButtonsState(
+            Map<AppPermissionViewModel.ButtonType, AppPermissionViewModel.ButtonState> states) {
+        setButtonState(mAllowPermissionPreference,
+                states.get(AppPermissionViewModel.ButtonType.ALLOW));
+        setButtonState(mAlwaysPermissionPreference,
+                states.get(AppPermissionViewModel.ButtonType.ALLOW_ALWAYS));
+        setButtonState(mForegroundOnlyPermissionPreference,
+                states.get(AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND));
+        setButtonState(mDenyPermissionPreference,
+                states.get(AppPermissionViewModel.ButtonType.DENY));
+    }
+
+    private void setButtonState(TwoStatePreference button,
+            AppPermissionViewModel.ButtonState state) {
+        button.setVisible(state.isShown());
+        if (state.isShown()) {
+            button.setChecked(state.isChecked());
+            button.setEnabled(state.isEnabled());
+        }
+    }
+
     /**
-     * Once we user has confirmed that he/she wants to revoke a permission that was granted by
-     * default, actually revoke the permissions.
-     *
-     * @param changeTarget whether to change foreground, background, or both.
-     * @see #showDefaultDenyDialog(int)
+     * Helper method to handle the UX edge case where the confirmation dialog is shown and two
+     * buttons are selected at once. This happens since the Auto UI doesn't use a proper radio
+     * group, so there is nothing that enforces that tapping on a button unchecks a previously
+     * checked button. Apart from this case, this UI is not necessary since the UI is entirely
+     * driven by the ViewModel.
      */
-    private void onDenyAnyWay(@ChangeTarget int changeTarget) {
-        boolean hasDefaultPermissions = false;
-        if ((changeTarget & CHANGE_FOREGROUND) != 0) {
-            if (mGroup.areRuntimePermissionsGranted()) {
-                SafetyNetLogger.logPermissionToggled(mGroup);
-            }
+    private void checkOnlyOneButtonOverride(AppPermissionViewModel.ButtonType buttonType) {
+        mAllowPermissionPreference.setChecked(
+                buttonType == AppPermissionViewModel.ButtonType.ALLOW);
+        mAlwaysPermissionPreference.setChecked(
+                buttonType == AppPermissionViewModel.ButtonType.ALLOW_ALWAYS);
+        mForegroundOnlyPermissionPreference.setChecked(
+                buttonType == AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND);
+        mDenyPermissionPreference.setChecked(buttonType == AppPermissionViewModel.ButtonType.DENY);
+    }
 
-            mGroup.revokeRuntimePermissions(false);
-            hasDefaultPermissions = mGroup.hasGrantedByDefaultPermission();
+    private void setDetail(Pair<Integer, Integer> detailResIds) {
+        if (detailResIds == null) {
+            mDetailsPreference.setVisible(false);
+            return;
         }
-        if ((changeTarget & CHANGE_BACKGROUND) != 0) {
-            if (mGroup.getBackgroundPermissions() != null) {
-                if (mGroup.getBackgroundPermissions().areRuntimePermissionsGranted()) {
-                    SafetyNetLogger.logPermissionToggled(mGroup.getBackgroundPermissions());
-                }
+        if (detailResIds.getSecond() != null) {
+            mDetailsPreference.setWidgetLayoutResource(R.layout.settings_preference_widget);
+            mDetailsPreference.setOnSecondTargetClickListener(
+                    v -> showAllPermissions(mPermGroupName));
+            mDetailsPreference.setSummary(
+                    getString(detailResIds.getFirst(), detailResIds.getSecond()));
+        } else {
+            mDetailsPreference.setSummary(detailResIds.getFirst());
+        }
+    }
 
-                mGroup.getBackgroundPermissions().revokeRuntimePermissions(false);
-                hasDefaultPermissions |=
-                        mGroup.getBackgroundPermissions().hasGrantedByDefaultPermission();
-            }
-        }
+    /**
+     * Show all individual permissions in this group in a new fragment.
+     */
+    private void showAllPermissions(@NonNull String filterGroup) {
+        Fragment frag = AutoAllAppPermissionsFragment.newInstance(mPackageName,
+                filterGroup, mUser,
+                getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID));
+        requireFragmentManager().beginTransaction()
+                .replace(android.R.id.content, frag)
+                .addToBackStack("AllPerms")
+                .commit();
+    }
 
-        if (hasDefaultPermissions || !mGroup.doesSupportRuntimePermissions()) {
-            mHasConfirmedRevoke = true;
+    private void setAdminSupportDetail(RestrictedLockUtils.EnforcedAdmin admin) {
+        if (admin != null) {
+            mDetailsPreference.setWidgetLayoutResource(R.layout.info_preference_widget);
+            mDetailsPreference.setOnSecondTargetClickListener(v ->
+                    RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin)
+            );
         }
-        updateUi();
+    }
+
+    /**
+     * Request to grant/revoke permissions group.
+     */
+    private void requestChange(AppPermissionViewModel.ChangeRequest changeRequest,
+            int buttonClicked) {
+        mViewModel.requestChange(/* setOneTime= */false, /* fragment= */ this,
+                /* defaultDeny= */this, changeRequest, buttonClicked);
     }
 
     /** Preference used to represent apps that can be picked as a default app. */
@@ -787,12 +399,14 @@
      * A dialog warning the user that they are about to deny a permission that was granted by
      * default.
      *
-     * @see #showDefaultDenyDialog(int)
+     * @see #showConfirmDialog(AppPermissionViewModel.ChangeRequest, int, int, boolean)
      */
     public static class DefaultDenyDialog extends DialogFragment {
         private static final String MSG = DefaultDenyDialog.class.getName() + ".arg.msg";
-        private static final String CHANGE_TARGET = DefaultDenyDialog.class.getName()
-                + ".arg.changeTarget";
+        private static final String CHANGE_REQUEST = DefaultDenyDialog.class.getName()
+                + ".arg.changeRequest";
+        private static final String BUTTON = DefaultDenyDialog.class.getName()
+                + ".arg.button";
 
         @NonNull
         @Override
@@ -801,31 +415,21 @@
             return new AlertDialogBuilder(getContext())
                     .setMessage(requireArguments().getInt(MSG))
                     .setNegativeButton(R.string.cancel,
-                            (dialog, which) -> fragment.updateUi())
+                            (dialog, which) -> dialog.cancel())
                     .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
                             (dialog, which) ->
-                                    fragment.onDenyAnyWay(requireArguments().getInt(CHANGE_TARGET)))
+                                    fragment.mViewModel.onDenyAnyWay(
+                                            (AppPermissionViewModel.ChangeRequest)
+                                                    getArguments().getSerializable(CHANGE_REQUEST),
+                                            getArguments().getInt(BUTTON),
+                                            false))
                     .create();
         }
-    }
-
-    /**
-     * A listener for permission changes.
-     */
-    private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
-        private final int mUid;
-
-        PermissionChangeListener(int uid) {
-            mUid = uid;
-        }
 
         @Override
-        public void onPermissionsChanged(int uid) {
-            if (uid == mUid) {
-                Log.w(LOG_TAG, "Permissions changed.");
-                mGroup = getAppPermissionGroup();
-                updateUi();
-            }
+        public void onCancel(DialogInterface dialog) {
+            AutoAppPermissionFragment fragment = (AutoAppPermissionFragment) getTargetFragment();
+            fragment.setRadioButtonsState(fragment.mViewModel.getButtonStateLiveData().getValue());
         }
     }
 }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java
index fed489e..659ed3d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java
@@ -17,52 +17,69 @@
 package com.android.permissioncontroller.permission.ui.auto;
 
 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
+import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED;
 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
 
+import static java.util.concurrent.TimeUnit.DAYS;
+
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
-import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.util.Log;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
 import androidx.fragment.app.Fragment;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceGroup;
-import androidx.preference.PreferenceScreen;
 
+import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.PermissionControllerStatsLog;
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
-import com.android.permissioncontroller.permission.model.AppPermissionGroup;
-import com.android.permissioncontroller.permission.model.AppPermissions;
+import com.android.permissioncontroller.permission.model.AppPermissionUsage;
+import com.android.permissioncontroller.permission.model.PermissionUsages;
 import com.android.permissioncontroller.permission.ui.Category;
 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel;
 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory;
-import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
 
 import java.text.Collator;
+import java.time.Instant;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 
 /** Screen to show the permissions for a specific application. */
-public class AutoAppPermissionsFragment extends AutoSettingsFrameFragment {
+public class AutoAppPermissionsFragment extends AutoSettingsFrameFragment implements
+        PermissionUsages.PermissionsUsagesChangeCallback {
     private static final String LOG_TAG = AutoAppPermissionsFragment.class.getSimpleName();
 
-    private static final String KEY_ALLOWED_PERMISSIONS_GROUP = "allowed_permissions_group";
-    private static final String KEY_DENIED_PERMISSIONS_GROUP = "denied_permissions_group";
+    private static final String IS_SYSTEM_PERMS_SCREEN = "_is_system_screen";
+    private static final String KEY_ALLOWED_PERMISSIONS_GROUP = Category.ALLOWED.getCategoryName();
+    private static final String KEY_DENIED_PERMISSIONS_GROUP = Category.DENIED.getCategoryName();
 
     private AppPermissionGroupsViewModel mViewModel;
 
-    private AppPermissions mAppPermissions;
-    private PreferenceScreen mExtraScreen;
     private String mPackageName;
+    private boolean mIsFirstLoad;
+    private UserHandle mUser;
+    private PermissionUsages mPermissionUsages;
+    private List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>();
+    private boolean mIsSystemPermsScreen;
 
     private Collator mCollator;
 
@@ -70,16 +87,19 @@
      * @return A new fragment
      */
     public static AutoAppPermissionsFragment newInstance(@NonNull String packageName,
-            @NonNull UserHandle userHandle) {
+            @NonNull UserHandle userHandle, long sessionId, boolean isSystemPermsScreen) {
         return setPackageNameAndUserHandle(new AutoAppPermissionsFragment(), packageName,
-                userHandle);
+                userHandle, sessionId, isSystemPermsScreen);
     }
 
     private static <T extends Fragment> T setPackageNameAndUserHandle(@NonNull T fragment,
-            @NonNull String packageName, @NonNull UserHandle userHandle) {
+            @NonNull String packageName, @NonNull UserHandle userHandle, long sessionId,
+            boolean isSystemPermsScreen) {
         Bundle arguments = new Bundle();
         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
+        arguments.putLong(EXTRA_SESSION_ID, sessionId);
+        arguments.putBoolean(IS_SYSTEM_PERMS_SCREEN, isSystemPermsScreen);
         fragment.setArguments(arguments);
         return fragment;
     }
@@ -90,6 +110,8 @@
         setLoading(true);
 
         mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
+        mUser = getArguments().getParcelable(Intent.EXTRA_USER);
+        mIsSystemPermsScreen = getArguments().getBoolean(IS_SYSTEM_PERMS_SCREEN, true);
         UserHandle userHandle = getArguments().getParcelable(Intent.EXTRA_USER);
         Activity activity = requireActivity();
         PackageInfo packageInfo = AutoPermissionsUtils.getPackageInfo(activity, mPackageName,
@@ -101,8 +123,6 @@
             return;
         }
 
-        mAppPermissions = new AppPermissions(activity, packageInfo, /* sortGroups= */ true,
-                () -> getActivity().finish());
         mCollator = Collator.getInstance(
                 getContext().getResources().getConfiguration().getLocales().get(0));
         AppPermissionGroupsViewModelFactory factory =
@@ -111,13 +131,27 @@
         mViewModel = new ViewModelProvider(this, factory).get(AppPermissionGroupsViewModel.class);
 
         setHeaderLabel(getContext().getString(R.string.app_permissions));
-        setAction(getContext().getString(R.string.all_permissions), v -> showAllPermissions());
+        if (mIsSystemPermsScreen) {
+            setAction(getContext().getString(R.string.all_permissions), v -> showAllPermissions());
+        }
         createPreferenceCategories(packageInfo);
 
         mViewModel.getPackagePermGroupsLiveData().observe(this, this::updatePreferences);
         if (mViewModel.getPackagePermGroupsLiveData().getValue() != null) {
             updatePreferences(mViewModel.getPackagePermGroupsLiveData().getValue());
         }
+
+        if (SdkLevel.isAtLeastS()) {
+            mPermissionUsages = new PermissionUsages(getContext());
+
+            long filterTimeBeginMillis = Math.max(System.currentTimeMillis()
+                            - DAYS.toMillis(
+                    AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS),
+                    Instant.EPOCH.toEpochMilli());
+            mPermissionUsages.load(null, null, filterTimeBeginMillis, Long.MAX_VALUE,
+                    PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(),
+                    false, false, this, false);
+        }
     }
 
     @Override
@@ -125,10 +159,27 @@
         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
     }
 
+
+    @Override
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void onPermissionUsagesChanged() {
+        if (mPermissionUsages.getUsages().isEmpty()) {
+            return;
+        }
+        if (getContext() == null) {
+            // Async result has come in after our context is gone.
+            return;
+        }
+
+        mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages());
+        updatePreferences(mViewModel.getPackagePermGroupsLiveData().getValue());
+    }
+
     private void showAllPermissions() {
         Fragment frag = AutoAllAppPermissionsFragment.newInstance(
                 getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
-                getArguments().getParcelable(Intent.EXTRA_USER));
+                getArguments().getParcelable(Intent.EXTRA_USER),
+                getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID));
         getFragmentManager().beginTransaction()
                 .replace(android.R.id.content, frag)
                 .addToBackStack("AllPerms")
@@ -155,10 +206,8 @@
         bindUi(packageInfo);
     }
 
-    // TODO(b/179383241): Make full use of groupMap data in this method
     private void updatePreferences(
             Map<Category, List<AppPermissionGroupsViewModel.GroupUiInfo>> groupMap) {
-        mAppPermissions.refresh();
         Context context = getPreferenceManager().getContext();
         if (context == null) {
             return;
@@ -173,108 +222,73 @@
             return;
         }
 
-        PreferenceCategory allowed = findPreference(KEY_ALLOWED_PERMISSIONS_GROUP);
-        PreferenceCategory denied = findPreference(KEY_DENIED_PERMISSIONS_GROUP);
+        Map<String, Long> groupUsageLastAccessTime = new HashMap<>();
+        mViewModel.extractGroupUsageLastAccessTime(groupUsageLastAccessTime, mAppPermissionUsages,
+                mPackageName);
 
-        allowed.removeAll();
-        denied.removeAll();
-
-        if (mExtraScreen != null) {
-            mExtraScreen.removeAll();
-            mExtraScreen.addPreference(AutoPermissionsUtils.createHeaderPreference(getContext(),
-                    mAppPermissions.getPackageInfo().applicationInfo));
-        }
-
-        Preference extraPerms = new Preference(context);
-        extraPerms.setIcon(R.drawable.ic_toc);
-        extraPerms.setTitle(R.string.additional_permissions);
-        boolean extraPermsAreAllowed = false;
-
-        ArrayList<AppPermissionGroup> groups = new ArrayList<>(
-                mAppPermissions.getPermissionGroups());
-        groups.sort((x, y) -> mCollator.compare(x.getLabel(), y.getLabel()));
-        allowed.setOrderingAsAdded(true);
-        denied.setOrderingAsAdded(true);
-
-        for (int i = 0; i < groups.size(); i++) {
-            AppPermissionGroup group = groups.get(i);
-            if (!Utils.shouldShowPermission(getContext(), group)) {
+        for (Category grantCategory : groupMap.keySet()) {
+            if (Category.ASK.equals(grantCategory)) {
+                // skip ask category for auto
                 continue;
             }
+            PreferenceCategory category = getPreferenceScreen().findPreference(
+                    grantCategory.getCategoryName());
+            if (grantCategory.equals(Category.ALLOWED_FOREGROUND)) {
+                category = findPreference(Category.ALLOWED.getCategoryName());
+            }
+            int numExtraPerms = 0;
 
-            boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
+            category.removeAll();
 
-            Preference preference = createPermissionPreference(getContext(), group);
-            if (isPlatform) {
-                PreferenceCategory category =
-                        group.areRuntimePermissionsGranted() ? allowed : denied;
-                category.addPreference(preference);
-            } else {
-                if (mExtraScreen == null) {
-                    mExtraScreen = getPreferenceManager().createPreferenceScreen(context);
-                    mExtraScreen.addPreference(
-                            AutoPermissionsUtils.createHeaderPreference(getContext(),
-                                    mAppPermissions.getPackageInfo().applicationInfo));
-                }
-                mExtraScreen.addPreference(preference);
-                if (group.areRuntimePermissionsGranted()) {
-                    extraPermsAreAllowed = true;
+
+            for (AppPermissionGroupsViewModel.GroupUiInfo groupInfo : groupMap.get(grantCategory)) {
+                if (groupInfo.isSystem() == mIsSystemPermsScreen) {
+                    Preference preference = createPermissionPreference(getContext(), groupInfo,
+                            groupUsageLastAccessTime);
+                    category.addPreference(preference);
+                } else if (!groupInfo.isSystem()) {
+                    numExtraPerms++;
                 }
             }
+
+
+            if (numExtraPerms > 0) {
+                setAdditionalPermissionsPreference(category, numExtraPerms, context);
+            }
+
+            if (category.getPreferenceCount() == 0) {
+                setNoPermissionPreference(category, grantCategory, context);
+            }
+
+            KotlinUtils.INSTANCE.sortPreferenceGroup(category, this::comparePreferences, false);
         }
 
-        if (mExtraScreen != null) {
-            extraPerms.setOnPreferenceClickListener(preference -> {
-                AutoAppPermissionsFragment.AdditionalPermissionsFragment
-                        frag = new AutoAppPermissionsFragment.AdditionalPermissionsFragment();
-                setPackageNameAndUserHandle(frag,
-                        getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
-                        getArguments().getParcelable(Intent.EXTRA_USER));
-                frag.setTargetFragment(AutoAppPermissionsFragment.this, 0);
-                getFragmentManager().beginTransaction()
-                        .replace(android.R.id.content, frag)
-                        .addToBackStack(null)
-                        .commit();
-                return true;
-            });
-            // Delete 1 to account for app header preference.
-            int count = mExtraScreen.getPreferenceCount() - 1;
-            extraPerms.setSummary(getResources().getQuantityString(
-                    R.plurals.additional_permissions_more, count,
-                    count));
-            PreferenceCategory category = extraPermsAreAllowed ? allowed : denied;
-            category.addPreference(extraPerms);
-        }
 
-        if (allowed.getPreferenceCount() == 0) {
-            Preference empty = new Preference(context);
-            empty.setTitle(getString(R.string.no_permissions_allowed));
-            empty.setSelectable(false);
-            allowed.addPreference(empty);
+        if (mIsFirstLoad) {
+            logAppPermissionsFragmentView();
+            mIsFirstLoad = false;
         }
-        if (denied.getPreferenceCount() == 0) {
-            Preference empty = new Preference(context);
-            empty.setTitle(getString(R.string.no_permissions_denied));
-            empty.setSelectable(false);
-            denied.addPreference(empty);
-        }
-
         setLoading(false);
     }
 
-    private Preference createPermissionPreference(Context context, AppPermissionGroup group) {
+    private Preference createPermissionPreference(Context context,
+            AppPermissionGroupsViewModel.GroupUiInfo groupInfo,
+            Map<String, Long> groupUsageLastAccessTime) {
+        String groupName = groupInfo.getGroupName();
         Preference preference = new Preference(context);
-        Drawable icon = Utils.loadDrawable(context.getPackageManager(),
-                group.getIconPkg(), group.getIconResId());
-        preference.setKey(group.getName());
-        preference.setTitle(group.getFullLabel());
-        preference.setIcon(Utils.applyTint(context, icon, android.R.attr.colorControlNormal));
-        preference.setSummary(getPreferenceSummary(group));
+        preference.setTitle(KotlinUtils.INSTANCE.getPermGroupLabel(context, groupName));
+        preference.setIcon(KotlinUtils.INSTANCE.getPermGroupIcon(context, groupName));
+        preference.setKey(groupName);
+        String summary = mViewModel.getPreferenceSummary(groupInfo, context,
+                groupUsageLastAccessTime.get(groupName));
+        if (!summary.isEmpty()) {
+            preference.setSummary(summary);
+        }
         preference.setOnPreferenceClickListener(pref -> {
             Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSION);
-            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, group.getApp().packageName);
-            intent.putExtra(Intent.EXTRA_PERMISSION_NAME, group.getPermissions().get(0).getName());
-            intent.putExtra(Intent.EXTRA_USER, group.getUser());
+            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName);
+            intent.putExtra(Intent.EXTRA_PERMISSION_NAME, groupName);
+            intent.putExtra(Intent.EXTRA_USER, mUser);
             intent.putExtra(EXTRA_CALLER_NAME, AutoAppPermissionsFragment.class.getName());
             context.startActivity(intent);
             return true;
@@ -282,39 +296,107 @@
         return preference;
     }
 
-    private String getPreferenceSummary(AppPermissionGroup group) {
-        return getGroupSummary(group);
+    private void setAdditionalPermissionsPreference(PreferenceCategory category, int numExtraPerms,
+            Context context) {
+        Preference extraPerms = new Preference(context);
+        extraPerms.setIcon(R.drawable.ic_toc);
+        extraPerms.setTitle(R.string.additional_permissions);
+        extraPerms.setOnPreferenceClickListener(preference -> {
+            AutoAppPermissionsFragment
+                    frag = AutoAppPermissionsFragment.newInstance(mPackageName, mUser,
+                    getArguments().getLong(EXTRA_SESSION_ID), false);
+            frag.setTargetFragment(AutoAppPermissionsFragment.this, 0);
+            getFragmentManager().beginTransaction()
+                    .replace(android.R.id.content, frag)
+                    .addToBackStack(null)
+                    .commit();
+            return true;
+        });
+        extraPerms.setSummary(getResources().getQuantityString(
+                R.plurals.additional_permissions_more, numExtraPerms,
+                numExtraPerms));
+        category.addPreference(extraPerms);
     }
 
-    private String getGroupSummary(AppPermissionGroup group) {
-        if (group.hasPermissionWithBackgroundMode() && group.areRuntimePermissionsGranted()) {
-            AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
-            if (backgroundGroup == null || !backgroundGroup.areRuntimePermissionsGranted()) {
-                return getContext().getString(R.string.permission_subtitle_only_in_foreground);
+    private void setNoPermissionPreference(PreferenceCategory category, Category grantCategory,
+            Context context) {
+        Preference empty = new Preference(context);
+        empty.setKey(getString(grantCategory.equals(Category.DENIED)
+                ? R.string.no_permissions_denied : R.string.no_permissions_allowed));
+        empty.setTitle(empty.getKey());
+        empty.setSelectable(false);
+        category.addPreference(empty);
+    }
+
+    private int comparePreferences(Preference lhs, Preference rhs) {
+        String additionalTitle = lhs.getContext().getString(R.string.additional_permissions);
+        if (lhs.getTitle().equals(additionalTitle)) {
+            return 1;
+        } else if (rhs.getTitle().equals(additionalTitle)) {
+            return -1;
+        }
+        return mCollator.compare(lhs.getTitle().toString(),
+                rhs.getTitle().toString());
+    }
+
+    private void logAppPermissionsFragmentView() {
+        Context context = getPreferenceManager().getContext();
+        if (context == null) {
+            return;
+        }
+        String permissionSubtitleOnlyInForeground =
+                context.getString(R.string.permission_subtitle_only_in_foreground);
+
+
+        long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
+        long viewId = new Random().nextLong();
+
+        PreferenceCategory allowed = findPreference(KEY_ALLOWED_PERMISSIONS_GROUP);
+
+        int numAllowed = allowed.getPreferenceCount();
+        for (int i = 0; i < numAllowed; i++) {
+            Preference preference = allowed.getPreference(i);
+            if (preference.getTitle().equals(getString(R.string.no_permissions_allowed))) {
+                // R.string.no_permission_allowed was added to PreferenceCategory
+                continue;
             }
+
+            int category = APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
+            if (preference.getSummary() != null
+                    && permissionSubtitleOnlyInForeground.contentEquals(preference.getSummary())) {
+                category = APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
+            }
+
+            logAppPermissionsFragmentViewEntry(sessionId, viewId, preference.getKey(), category);
         }
-        return null;
+
+        PreferenceCategory denied = findPreference(KEY_DENIED_PERMISSIONS_GROUP);
+
+        int numDenied = denied.getPreferenceCount();
+        for (int i = 0; i < numDenied; i++) {
+            Preference preference = denied.getPreference(i);
+            if (preference.getTitle().equals(getString(R.string.no_permissions_denied))) {
+                // R.string.no_permission_denied was added to PreferenceCategory
+                continue;
+            }
+            logAppPermissionsFragmentViewEntry(sessionId, viewId, preference.getKey(),
+                    APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED);
+        }
     }
 
-    /**
-     * Class that shows additional permissions.
-     */
-    public static class AdditionalPermissionsFragment extends AutoSettingsFrameFragment {
-        AutoAppPermissionsFragment mOuterFragment;
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            // Set this before calling super.onCreate as it is needed in onCreatePreferences
-            // (which is called from super.onCreate).
-            mOuterFragment = (AutoAppPermissionsFragment) getTargetFragment();
-            super.onCreate(savedInstanceState);
-            setHeaderLabel(mOuterFragment.getHeaderLabel());
+    private void logAppPermissionsFragmentViewEntry(
+            long sessionId, long viewId, String permissionGroupName, int category) {
+        Integer uid = KotlinUtils.INSTANCE.getPackageUid(getActivity().getApplication(),
+                mPackageName, mUser);
+        if (uid == null) {
+            return;
         }
-
-        @Override
-        public void onCreatePreferences(Bundle bundle, String s) {
-            setPreferenceScreen(mOuterFragment.mExtraScreen);
-        }
+        PermissionControllerStatsLog.write(APP_PERMISSIONS_FRAGMENT_VIEWED, sessionId, viewId,
+                permissionGroupName, uid, mPackageName, category);
+        Log.v(LOG_TAG, "AutoAppPermissionFragment view logged with sessionId=" + sessionId
+                + " viewId=" + viewId + " permissionGroupName=" + permissionGroupName + " uid="
+                + uid + " packageName="
+                + mPackageName + " category=" + category);
     }
 }
 
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageCustomPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageCustomPermissionsFragment.java
deleted file mode 100644
index 0de51c8..0000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageCustomPermissionsFragment.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2019 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.permissioncontroller.permission.ui.auto;
-
-import com.android.permissioncontroller.R;
-
-/** Shows additional non-system permissions that can be granted/denied. */
-public class AutoManageCustomPermissionsFragment extends AutoManagePermissionsFragment {
-
-    @Override
-    protected int getScreenHeaderRes() {
-        return R.string.additional_permissions;
-    }
-
-    @Override
-    protected void updatePermissionsUi() {
-        updatePermissionsUi(/* addSystemPermissions= */ false);
-    }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageOtherPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageOtherPermissionsFragment.java
new file mode 100644
index 0000000..e603399
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageOtherPermissionsFragment.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2021 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.permissioncontroller.permission.ui.auto;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
+import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo;
+import com.android.permissioncontroller.permission.ui.model.ManagePermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.PermissionGroupPreferenceUtils;
+
+import java.util.List;
+
+/**
+ * Shows additional non-system permissions that can be granted/denied.
+ *
+ * Largely based on
+ * {@link com.android.permissioncontroller.permission.ui.television.ManagePermissionsOtherFragment}.
+ */
+public class AutoManageOtherPermissionsFragment extends AutoSettingsFrameFragment {
+    private static final String KEY_UNUSED_CATEGORY = "category_unused";
+    private static final String KEY_ADDITIONAL_CATEGORY = "category_additional";
+
+    private PreferenceCategory mUnusedCategory;
+    private PreferenceCategory mAdditionalCategory;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setLoading(true);
+        setHeaderLabel(getString(R.string.other_permissions_label));
+
+        final Application application = getActivity().getApplication();
+        final ViewModelProvider.Factory factory =
+                ViewModelProvider.AndroidViewModelFactory.getInstance(application);
+        final ManagePermissionsViewModel viewModel = new ViewModelProvider(this, factory)
+                .get(ManagePermissionsViewModel.class);
+
+        viewModel.getUnusedPermissionGroups().observe(this, this::onUnusedPermissionGroupsChanged);
+        viewModel.getAdditionalPermissionGroups().observe(this,
+                this::onAdditionalPermissionGroupsChanged);
+        viewModel.hasUnusedOrAdditionalPermissionGroups().observe(this,
+                this::onHasUnusedOrAdditionalPermissionGroups);
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle bundle, String s) {
+        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        final Context context = getPreferenceManager().getContext();
+        final PreferenceScreen screen = getPreferenceScreen();
+        // We add categories here, but make them invisible until the data is loaded.
+        mUnusedCategory = new PreferenceCategory(context);
+        mUnusedCategory.setKey(KEY_UNUSED_CATEGORY);
+        mUnusedCategory.setTitle(R.string.not_used_permissions_label);
+        mUnusedCategory.setSummary(R.string.not_used_permissions_description);
+        mUnusedCategory.setVisible(false);
+        screen.addPreference(mUnusedCategory);
+
+        mAdditionalCategory = new PreferenceCategory(context);
+        mAdditionalCategory.setKey(KEY_ADDITIONAL_CATEGORY);
+        mAdditionalCategory.setTitle(R.string.additional_permissions_label);
+        mAdditionalCategory.setSummary(R.string.additional_permissions_description);
+        mAdditionalCategory.setVisible(false);
+        screen.addPreference(mAdditionalCategory);
+    }
+
+    private void onUnusedPermissionGroupsChanged(List<PermGroupPackagesUiInfo> permissionGroups) {
+        updateCategory(mUnusedCategory, permissionGroups);
+    }
+
+    private void onAdditionalPermissionGroupsChanged(
+            List<PermGroupPackagesUiInfo> permissionGroups) {
+        updateCategory(mAdditionalCategory, permissionGroups);
+    }
+
+    private void onHasUnusedOrAdditionalPermissionGroups(Boolean hasPGs) {
+        if (!hasPGs) {
+            // There are not more permissions on this screen - go back.
+            getParentFragmentManager().popBackStack();
+        }
+    }
+
+    private void updateCategory(PreferenceCategory category,
+            List<PermGroupPackagesUiInfo> permissionGroups) {
+        final Context context = getPreferenceManager().getContext();
+        if (context == null) {
+            return;
+        }
+
+        PermissionGroupPreferenceUtils.updateGroupOfPermissionPreferences(context, category,
+                permissionGroups);
+        // Only show the category if it's not empty.
+        category.setVisible(!permissionGroups.isEmpty());
+
+        setLoading(false);
+    }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManagePermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManagePermissionsFragment.java
deleted file mode 100644
index 49ea5eb..0000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManagePermissionsFragment.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 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.permissioncontroller.permission.ui.auto;
-
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.preference.Preference;
-
-import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
-import com.android.permissioncontroller.permission.model.legacy.PermissionGroup;
-import com.android.permissioncontroller.permission.model.legacy.PermissionGroups;
-import com.android.permissioncontroller.permission.utils.Utils;
-
-import java.text.Collator;
-import java.util.ArrayList;
-
-/** Base class to show the list of permissions that can be granted/denied. */
-abstract class AutoManagePermissionsFragment extends AutoSettingsFrameFragment implements
-        PermissionGroups.PermissionsGroupsChangeCallback, Preference.OnPreferenceClickListener {
-
-    private static final String LOG_TAG = "ManagePermissionsFragment";
-
-    static final String OS_PKG = "android";
-
-    private PermissionGroups mPermissions;
-
-    private Collator mCollator;
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setLoading(true);
-
-        mPermissions = new PermissionGroups(getContext(), requireActivity().getLoaderManager(),
-                /* callback= */ this, /* getAppUiInfo= */ false,
-                /* getNonPlatformPermissions= */ true);
-        mCollator = Collator.getInstance(
-                getContext().getResources().getConfiguration().getLocales().get(0));
-
-        setHeaderLabel(getString(getScreenHeaderRes()));
-    }
-
-    @Override
-    public void onCreatePreferences(Bundle bundle, String s) {
-        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
-    }
-
-    @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;
-    }
-
-    /** Returns the header string resource. */
-    @StringRes
-    protected abstract int getScreenHeaderRes();
-
-    /** Returns the current permissions. */
-    protected PermissionGroups getPermissions() {
-        return mPermissions;
-    }
-
-    @Override
-    public void onPermissionGroupsChanged() {
-        updatePermissionsUi();
-    }
-
-    /** Update the preferences to show the new {@link #getPermissions() permissions}. */
-    protected abstract void updatePermissionsUi();
-
-    /**
-     * Add preferences for all permissions of a type to the preference screen.
-     */
-    protected void updatePermissionsUi(boolean addSystemPermissions) {
-        Context context = getPreferenceManager().getContext();
-        if (context == null || getActivity() == null) {
-            return;
-        }
-
-        ArrayList<PermissionGroup> groups = new ArrayList<>(mPermissions.getGroups());
-        groups.sort((x, y) -> mCollator.compare(x.getLabel(), y.getLabel()));
-        getPreferenceScreen().removeAll();
-        getPreferenceScreen().setOrderingAsAdded(true);
-
-        // Use this to speed up getting the info for all of the PermissionApps below.
-        // Create a new one for each refresh to make sure it has fresh data.
-        for (int i = 0; i < groups.size(); i++) {
-            PermissionGroup group = groups.get(i);
-            boolean isSystemPermission = group.getDeclaringPackage().equals(OS_PKG);
-
-            if (addSystemPermissions == isSystemPermission) {
-                Preference preference = findPreference(group.getName());
-
-                if (preference == null) {
-                    preference = new Preference(context);
-                    preference.setOnPreferenceClickListener(this);
-                    preference.setKey(group.getName());
-                    preference.setIcon(Utils.applyTint(context, group.getIcon(),
-                            android.R.attr.colorControlNormal));
-                    preference.setTitle(group.getLabel());
-                    // Set blank summary so that no resizing/jumping happens when the summary is
-                    // loaded.
-                    preference.setSummary(" ");
-                    preference.setPersistent(false);
-                    getPreferenceScreen().addPreference(preference);
-                }
-                preference.setSummary(
-                        getString(R.string.app_permissions_group_summary, group.getGranted(),
-                                group.getTotal()));
-            }
-        }
-        if (getPreferenceScreen().getPreferenceCount() != 0) {
-            setLoading(false);
-        }
-    }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageStandardPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageStandardPermissionsFragment.java
index c3d9ed2..8bbcde6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageStandardPermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoManageStandardPermissionsFragment.java
@@ -16,31 +16,43 @@
 
 package com.android.permissioncontroller.permission.ui.auto;
 
+
 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
 
 import android.app.Application;
+import android.content.Context;
 import android.os.Bundle;
 
 import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentTransaction;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
 
 import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.model.legacy.PermissionGroup;
+import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
+import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo;
 import com.android.permissioncontroller.permission.ui.UnusedAppsFragment;
+import com.android.permissioncontroller.permission.ui.model.ManagePermissionsViewModel;
 import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.PermissionGroupPreferenceUtils;
 import com.android.permissioncontroller.permission.utils.Utils;
 
 import java.util.List;
 
-/** Shows the standard permissions that can be granted/denied. */
-public class AutoManageStandardPermissionsFragment extends AutoManagePermissionsFragment {
+/**
+ * Shows the standard permissions that can be granted/denied.
+ *
+ * Largely based on the implementation of
+ * {@link com.android.permissioncontroller.permission.ui.television.ManagePermissionsFragment}.
+ */
+public class AutoManageStandardPermissionsFragment extends AutoSettingsFrameFragment {
 
-    private static final String EXTRA_PREFS_KEY = "extra_prefs_key";
-    private static final String AUTO_REVOKE_KEY = "auto_revoke_key";
-    private ManageStandardPermissionsViewModel mViewModel;
+    private static final String KEY_OTHER_PERMISSIONS = "other_permissions";
+    private static final String KEY_AUTO_REVOKE = "auto_revoke_key";
+    private ManagePermissionsViewModel mManagePermissionsViewModel;
+    private ManageStandardPermissionsViewModel mManageStandardPermissionsViewModel;
 
     /** Returns a new instance of {@link AutoManageStandardPermissionsFragment}. */
     public static AutoManageStandardPermissionsFragment newInstance() {
@@ -50,84 +62,108 @@
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setLoading(true);
+        setHeaderLabel(getString(R.string.app_permission_manager));
 
         final Application application = getActivity().getApplication();
-        mViewModel = new ViewModelProvider(this,
+        final ViewModelProvider.Factory viewModelFactory =
+                ViewModelProvider.AndroidViewModelFactory.getInstance(application);
+        mManagePermissionsViewModel = new ViewModelProvider(this, viewModelFactory)
+                .get(ManagePermissionsViewModel.class);
+        mManageStandardPermissionsViewModel = new ViewModelProvider(this,
                 ViewModelProvider.AndroidViewModelFactory.getInstance(application))
                 .get(ManageStandardPermissionsViewModel.class);
-
-        mViewModel.getUiDataLiveData().observe(this, permissionGroups -> {
-            if (permissionGroups != null) {
-                updatePermissionsUi();
-            } else {
-                getActivity().finish();
-            }
-        });
-
-        mViewModel.getNumAutoRevoked().observe(this, show -> updatePermissionsUi());
+        mManagePermissionsViewModel.getUsedPermissionGroups().observe(this,
+                this::onPermissionGroupsChanged);
+        mManageStandardPermissionsViewModel.getNumAutoRevoked().observe(this,
+                this::onNumAutoRevokedChanged);
     }
 
     @Override
-    protected int getScreenHeaderRes() {
-        return R.string.app_permission_manager;
+    public void onCreatePreferences(Bundle bundle, String s) {
+        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
     }
 
-    @Override
-    protected void updatePermissionsUi() {
-        updatePermissionsUi(/* addSystemPermissions= */ true);
-
-        // Check if we need an additional permissions preference
-        List<PermissionGroup> groups = getPermissions().getGroups();
-        int numExtraPermissions = 0;
-        for (PermissionGroup group : groups) {
-            if (!group.getDeclaringPackage().equals(AutoManagePermissionsFragment.OS_PKG)) {
-                numExtraPermissions++;
-            }
+    private void onPermissionGroupsChanged(List<PermGroupPackagesUiInfo> permissionGroups) {
+        final Context context = getPreferenceManager().getContext();
+        if (context == null) {
+            return;
         }
 
-        Preference additionalPermissionsPreference = getPreferenceScreen().findPreference(
-                EXTRA_PREFS_KEY);
-        if (numExtraPermissions == 0) {
-            if (additionalPermissionsPreference != null) {
-                getPreferenceScreen().removePreference(additionalPermissionsPreference);
-            }
+        final PreferenceScreen screen = getPreferenceScreen();
+
+        // First check if "Other preferences" button exists. If it does: remove it, but hold on to
+        // the reference - we'll it add back later, after the preferences for the permission groups
+        // have been updated. If it does not exist: create and hold on to it.
+        Preference otherPermissionsPreference = screen.findPreference(KEY_OTHER_PERMISSIONS);
+        if (otherPermissionsPreference == null) {
+            otherPermissionsPreference = buildOtherPermissionsPreference(context);
         } else {
-            if (additionalPermissionsPreference == null) {
-                additionalPermissionsPreference = new Preference(
-                        getPreferenceManager().getContext());
-                additionalPermissionsPreference.setKey(EXTRA_PREFS_KEY);
-                additionalPermissionsPreference.setIcon(Utils.applyTint(getActivity(),
-                        R.drawable.ic_more_items,
-                        android.R.attr.colorControlNormal));
-                additionalPermissionsPreference.setTitle(R.string.additional_permissions);
-                additionalPermissionsPreference.setOnPreferenceClickListener(preference -> {
-                    AutoManageCustomPermissionsFragment frag =
-                            new AutoManageCustomPermissionsFragment();
-                    frag.setTargetFragment(AutoManageStandardPermissionsFragment.this,
-                            /* requestCode= */ 0);
-                    FragmentTransaction ft = getFragmentManager().beginTransaction();
-                    ft.replace(android.R.id.content, frag);
-                    ft.addToBackStack(null);
-                    ft.commit();
-                    return true;
-                });
-
-                getPreferenceScreen().addPreference(additionalPermissionsPreference);
-            }
-
-            additionalPermissionsPreference.setSummary(getResources().getQuantityString(
-                    R.plurals.additional_permissions_more, numExtraPermissions,
-                    numExtraPermissions));
+            screen.removePreference(otherPermissionsPreference);
+            // The PreferenceScreen is ordering items as added
+            // (see PreferenceGroup#setOrderingAsAdded()), which means that it assigns positional
+            // indexes ("order") to Preferences incrementally as they are added, BUT ONLY IF their
+            // current "order" is the DEFAULT_ORDER.
+            // However, when the Preferences are removed from the group they keep their "order" and
+            // thus when they are re-added to a group (same or another) their "order" does not get
+            // re-assigned, so they may show up at the position they previously were at.
+            // We want the otherPermissionsPreference to always be the last in the list, so reset
+            // its "order" to DEFAULT, so that we add last to the group, it indeed goes into the
+            // last position.
+            otherPermissionsPreference.setOrder(Preference.DEFAULT_ORDER);
         }
 
-        Integer numAutoRevoked = mViewModel.getNumAutoRevoked().getValue();
+        PermissionGroupPreferenceUtils.updateGroupOfPermissionPreferences(context, screen,
+                permissionGroups);
 
-        Preference autoRevokePreference = getPreferenceScreen().findPreference(AUTO_REVOKE_KEY);
+        screen.addPreference(otherPermissionsPreference);
+
+        // load initial auto-revoke count, if it is ready
+        Integer numAutoRevoked = mManageStandardPermissionsViewModel.getNumAutoRevoked().getValue();
+        onNumAutoRevokedChanged(numAutoRevoked);
+
+        setLoading(false);
+    }
+
+
+    private Preference buildOtherPermissionsPreference(Context context) {
+        final Preference preference = new Preference(context);
+        preference.setPersistent(false);
+        preference.setKey(KEY_OTHER_PERMISSIONS);
+        preference.setTitle(R.string.other_permissions_label);
+        preference.setIcon(
+                Utils.applyTint(
+                        context, R.drawable.ic_more_items, android.R.attr.colorControlNormal));
+        preference.setOnPreferenceClickListener(p -> {
+            AutoManageOtherPermissionsFragment frag =
+                    new AutoManageOtherPermissionsFragment();
+            frag.setTargetFragment(AutoManageStandardPermissionsFragment.this,
+                    /* requestCode= */ 0);
+            FragmentTransaction ft = getFragmentManager().beginTransaction();
+            ft.replace(android.R.id.content, frag);
+            ft.addToBackStack(null);
+            ft.commit();
+            return true;
+        });
+        // Make invisible for now and subscribe to the LiveData that tracks whether there are any
+        // unused or additional permissions.
+        preference.setVisible(false);
+        mManagePermissionsViewModel.hasUnusedOrAdditionalPermissionGroups().observe(this,
+                preference::setVisible);
+        return preference;
+    }
+
+    private void onNumAutoRevokedChanged(Integer numAutoRevoked) {
+        // to prevent ui jank, don't display auto-revoke until categories have loaded
+        if (mManagePermissionsViewModel.getUsedPermissionGroups().getValue() == null) {
+            return;
+        }
+        Preference autoRevokePreference = getPreferenceScreen().findPreference(KEY_AUTO_REVOKE);
         if (numAutoRevoked != null && numAutoRevoked != 0) {
             if (autoRevokePreference == null) {
                 autoRevokePreference = new Preference(getPreferenceManager().getContext());
                 autoRevokePreference.setOrder(-1);
-                autoRevokePreference.setKey(AUTO_REVOKE_KEY);
+                autoRevokePreference.setKey(KEY_AUTO_REVOKE);
                 autoRevokePreference.setSingleLineTitle(false);
                 autoRevokePreference.setIcon(R.drawable.ic_info_outline);
                 autoRevokePreference.setTitle(
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoPermissionAppsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoPermissionAppsFragment.java
index dfd5361..94fd6b9 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoPermissionAppsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoPermissionAppsFragment.java
@@ -16,82 +16,272 @@
 
 package com.android.permissioncontroller.permission.ui.auto;
 
+import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
+import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
+import static com.android.permissioncontroller.permission.ui.Category.ALLOWED;
+import static com.android.permissioncontroller.permission.ui.Category.ALLOWED_FOREGROUND;
+import static com.android.permissioncontroller.permission.ui.Category.ASK;
+import static com.android.permissioncontroller.permission.ui.Category.DENIED;
+import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
+
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceGroup;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
-import com.android.permissioncontroller.permission.model.AppPermissionGroup;
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps.Callback;
-import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFragment;
-import com.android.permissioncontroller.permission.ui.handheld.PermissionControlPreference;
+import com.android.permissioncontroller.permission.model.AppPermissionUsage;
+import com.android.permissioncontroller.permission.model.PermissionUsages;
+import com.android.permissioncontroller.permission.ui.Category;
+import com.android.permissioncontroller.permission.ui.handheld.SmartIconLoadPackagePermissionPreference;
+import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel;
+import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModelFactory;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
 import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.settingslib.utils.applications.AppUtils;
 
 import java.text.Collator;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
+import java.util.Random;
+
+import kotlin.Pair;
 
 /** Shows the list of applications which have (or do not have) the given permission. */
-public class AutoPermissionAppsFragment extends AutoSettingsFrameFragment implements Callback {
+public class AutoPermissionAppsFragment extends AutoSettingsFrameFragment implements
+        PermissionUsages.PermissionsUsagesChangeCallback {
 
-    private static final String KEY_SHOW_SYSTEM_PREFS = "_showSystem";
-    private static final String KEY_ALLOWED_PERMISSIONS_GROUP = "allowed_permissions_group";
-    private static final String KEY_ALLOWED_FOREGROUND_PERMISSIONS_GROUP =
-            "allowed_foreground_permissions_group";
-    private static final String KEY_DENIED_PERMISSIONS_GROUP = "denied_permissions_group";
-
-    private static final String SHOW_SYSTEM_KEY = AutoPermissionAppsFragment.class.getName()
-            + KEY_SHOW_SYSTEM_PREFS;
+    private static final String LOG_TAG = "AutoPermissionAppsFragment";
+    private static final String KEY_EMPTY = "_empty";
 
     /** Creates a new instance of {@link AutoPermissionAppsFragment} for the given permission. */
-    public static AutoPermissionAppsFragment newInstance(String permissionName) {
-        return setPermissionName(new AutoPermissionAppsFragment(), permissionName);
+    public static AutoPermissionAppsFragment newInstance(String permissionName, long sessionId) {
+        return setPermissionName(new AutoPermissionAppsFragment(), permissionName, sessionId);
     }
 
-    private static <T extends Fragment> T setPermissionName(T fragment, String permissionName) {
+    private static <T extends Fragment> T setPermissionName(
+            T fragment, String permissionName, long sessionId) {
         Bundle arguments = new Bundle();
         arguments.putString(Intent.EXTRA_PERMISSION_NAME, permissionName);
+        arguments.putLong(EXTRA_SESSION_ID, sessionId);
         fragment.setArguments(arguments);
         return fragment;
     }
 
-    private PermissionApps mPermissionApps;
-
-    private boolean mShowSystem;
-    private boolean mHasSystemApps;
+    private PermissionAppsViewModel mViewModel;
+    private PermissionUsages mPermissionUsages;
+    private List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>();
+    private String mPermGroupName;
 
     private Collator mCollator;
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        if (savedInstanceState != null) {
-            mShowSystem = savedInstanceState.getBoolean(SHOW_SYSTEM_KEY);
-        }
-
         setLoading(true);
 
-        String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
-        mPermissionApps = new PermissionApps(getActivity(), groupName, /* callback= */ this);
-        mPermissionApps.refresh(/* getUiInfo= */ true);
+        mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
+        if (mPermGroupName == null) {
+            mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
+        }
+
+        Drawable icon = KotlinUtils.INSTANCE.getPermGroupIcon(getContext(), mPermGroupName);
+        CharSequence label = KotlinUtils.INSTANCE.getPermGroupLabel(getContext(), mPermGroupName);
+        CharSequence description = KotlinUtils.INSTANCE.getPermGroupDescription(getContext(),
+                mPermGroupName);
+
+        setHeaderLabel(label);
+        Preference header = new Preference(getContext());
+        header.setTitle(label);
+        header.setIcon(icon);
+        header.setSummary(Utils.getPermissionGroupDescriptionString(getContext(), mPermGroupName,
+                description));
+        getPreferenceScreen().addPreference(header);
 
         mCollator = Collator.getInstance(
                 getContext().getResources().getConfiguration().getLocales().get(0));
 
-        setShowSystemAppsToggle();
-        bindUi(mPermissionApps, groupName);
+        PermissionAppsViewModelFactory factory =
+                new PermissionAppsViewModelFactory(getActivity().getApplication(), mPermGroupName,
+                        this, new Bundle());
+        mViewModel = new ViewModelProvider(this, factory).get(PermissionAppsViewModel.class);
+
+        mViewModel.getCategorizedAppsLiveData().observe(this, this::onPackagesLoaded);
+        mViewModel.getShouldShowSystemLiveData().observe(this, this::updateMenu);
+        mViewModel.getHasSystemAppsLiveData().observe(this, this::hideSystemAppToggleIfNecessary);
+
+        // If the build type is below S, the app ops for permission usage can't be found. Thus, we
+        // shouldn't load permission usages, for them.
+        if (SdkLevel.isAtLeastS()) {
+            Context context = getPreferenceManager().getContext();
+            mPermissionUsages = new PermissionUsages(context);
+
+            long filterTimeBeginMillis = mViewModel.getFilterTimeBeginMillis();
+            mPermissionUsages.load(null, null, filterTimeBeginMillis, Long.MAX_VALUE,
+                    PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(),
+                    false, false, this, false);
+        }
+    }
+
+    private void updateMenu(Boolean showSystem) {
+        if (showSystem == null) {
+            showSystem = false;
+        }
+        // Show the opposite label from the current state.
+        String label;
+        if (showSystem) {
+            label = getString(R.string.menu_hide_system);
+        } else {
+            label = getString(R.string.menu_show_system);
+        }
+
+        boolean showSystemFinal = showSystem;
+        setAction(label, v -> mViewModel.updateShowSystem(!showSystemFinal));
+    }
+
+    /**
+     * Main differences between this phone implementation and this one are:
+     * <ul>
+     *     <li>No special handling for scoped storage</li>
+     * </ul>
+     */
+    private void onPackagesLoaded(Map<Category, List<Pair<String, UserHandle>>> categories) {
+        Preference additionalPermissionsPreference = getPreferenceScreen().findPreference(
+                ALLOWED_FOREGROUND.getCategoryName());
+        if (additionalPermissionsPreference == null) {
+            // This preference resources includes the "Ask" permission group. That's okay for Auto
+            // even though Auto doesn't support the one-time permission because the code later in
+            // this method will hide unused permission groups.
+            addPreferencesFromResource(R.xml.allowed_denied);
+        }
+        // Hide allowed foreground label by default, to avoid briefly showing it before updating
+        findPreference(ALLOWED_FOREGROUND.getCategoryName()).setVisible(false);
+        Context context = getPreferenceManager().getContext();
+
+        if (context == null || getActivity() == null || categories == null) {
+            return;
+        }
+
+        Map<String, Preference> existingPrefs = new ArrayMap<>();
+
+        // Start at 1 since the header preference will always be in the 0th index
+        for (int i = 1; i < getPreferenceScreen().getPreferenceCount(); i++) {
+            PreferenceCategory category = (PreferenceCategory)
+                    getPreferenceScreen().getPreference(i);
+            category.setOrderingAsAdded(true);
+            int numPreferences = category.getPreferenceCount();
+            for (int j = 0; j < numPreferences; j++) {
+                Preference preference = category.getPreference(j);
+                existingPrefs.put(preference.getKey(), preference);
+            }
+            category.removeAll();
+        }
+
+        long viewIdForLogging = new Random().nextLong();
+        long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
+
+        Boolean showAlways = mViewModel.getShowAllowAlwaysStringLiveData().getValue();
+        if (showAlways != null && showAlways) {
+            findPreference(ALLOWED.getCategoryName()).setTitle(R.string.allowed_always_header);
+        } else {
+            findPreference(ALLOWED.getCategoryName()).setTitle(R.string.allowed_header);
+        }
+
+        // A mapping of user + packageName to their last access timestamps for the permission group.
+        Map<String, Long> groupUsageLastAccessTime =
+                mViewModel.extractGroupUsageLastAccessTime(mAppPermissionUsages);
+
+        for (Category grantCategory : categories.keySet()) {
+            List<Pair<String, UserHandle>> packages = categories.get(grantCategory);
+            PreferenceCategory category = findPreference(grantCategory.getCategoryName());
+
+            // If this category is empty set up the empty preference.
+            if (packages.size() == 0) {
+                Preference empty = new Preference(context);
+                empty.setSelectable(false);
+                empty.setKey(category.getKey() + KEY_EMPTY);
+                if (grantCategory.equals(ALLOWED)) {
+                    empty.setTitle(getString(R.string.no_apps_allowed));
+                } else if (grantCategory.equals(ALLOWED_FOREGROUND)) {
+                    category.setVisible(false);
+                } else if (grantCategory.equals(ASK)) {
+                    category.setVisible(false);
+                } else {
+                    empty.setTitle(getString(R.string.no_apps_denied));
+                }
+                category.addPreference(empty);
+                continue;
+            } else if (grantCategory.equals(ALLOWED_FOREGROUND)) {
+                category.setVisible(true);
+            } else if (grantCategory.equals(ASK)) {
+                category.setVisible(true);
+            }
+
+            for (Pair<String, UserHandle> packageUserLabel : packages) {
+                String packageName = packageUserLabel.getFirst();
+                UserHandle user = packageUserLabel.getSecond();
+
+                String key = user + packageName;
+
+                Long lastAccessTime = groupUsageLastAccessTime.get(key);
+                Pair<String, Integer> summaryTimestamp = Utils
+                        .getPermissionLastAccessSummaryTimestamp(
+                                lastAccessTime, context, mPermGroupName);
+
+                Preference existingPref = existingPrefs.get(key);
+                if (existingPref != null) {
+                    updatePreferenceSummary(existingPref, summaryTimestamp);
+                    category.addPreference(existingPref);
+                    continue;
+                }
+
+                SmartIconLoadPackagePermissionPreference pref =
+                        new SmartIconLoadPackagePermissionPreference(getActivity().getApplication(),
+                                packageName, user, context);
+                pref.setKey(key);
+                pref.setTitle(KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(),
+                        packageName, user));
+                pref.setOnPreferenceClickListener((Preference p) -> {
+                    Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSION);
+                    intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+                    intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermGroupName);
+                    intent.putExtra(Intent.EXTRA_USER, user);
+                    intent.putExtra(EXTRA_CALLER_NAME, getClass().getName());
+                    intent.putExtra(EXTRA_SESSION_ID, sessionId);
+                    startActivity(intent);
+                    return true;
+                });
+                pref.setTitleContentDescription(AppUtils.getAppContentDescription(context,
+                        packageName, user.getIdentifier()));
+
+                updatePreferenceSummary(pref, summaryTimestamp);
+
+                category.addPreference(pref);
+                if (!mViewModel.getCreationLogged()) {
+                    logPermissionAppsFragmentCreated(packageName, user, viewIdForLogging,
+                            grantCategory.equals(ALLOWED), grantCategory.equals(ALLOWED_FOREGROUND),
+                            grantCategory.equals(DENIED));
+                }
+            }
+            KotlinUtils.INSTANCE.sortPreferenceGroup(category, this::comparePreference, false);
+        }
+
+        mViewModel.setCreationLogged(true);
+
+        setLoading(false);
     }
 
     @Override
@@ -99,206 +289,44 @@
         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
     }
 
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-
-        outState.putBoolean(SHOW_SYSTEM_KEY, mShowSystem);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mPermissionApps.refresh(/* getUiInfo= */ true);
-    }
-
-    private void setShowSystemAppsToggle() {
-        if (!mHasSystemApps) {
+    private void hideSystemAppToggleIfNecessary(Boolean hasSystemApps) {
+        if (hasSystemApps == null || !hasSystemApps) {
             setAction(/* label= */ null, /* onClickListener= */ null);
-            return;
         }
-
-        // Show the opposite label from the current state.
-        String label;
-        if (mShowSystem) {
-            label = getString(R.string.menu_hide_system);
-        } else {
-            label = getString(R.string.menu_show_system);
-        }
-
-        setAction(label, v -> {
-            mShowSystem = !mShowSystem;
-            if (mPermissionApps.getApps() != null) {
-                onPermissionsLoaded(mPermissionApps);
-            }
-            setShowSystemAppsToggle();
-        });
     }
 
-    private void bindUi(PermissionApps permissionApps, @NonNull String groupName) {
-        CharSequence label = permissionApps.getFullLabel();
-        setHeaderLabel(label);
-
-        Drawable icon = permissionApps.getIcon();
-        Preference header = new Preference(getContext());
-        header.setTitle(label);
-        header.setIcon(icon);
-        header.setSummary(Utils.getPermissionGroupDescriptionString(getContext(), groupName,
-                permissionApps.getDescription()));
-        getPreferenceScreen().addPreference(header);
-
-        PreferenceGroup allowed = new PreferenceCategory(getContext());
-        allowed.setKey(KEY_ALLOWED_PERMISSIONS_GROUP);
-        allowed.setTitle(R.string.allowed_header);
-        allowed.setVisible(false);
-        getPreferenceScreen().addPreference(allowed);
-
-        PreferenceGroup foreground = new PreferenceCategory(getContext());
-        foreground.setKey(KEY_ALLOWED_FOREGROUND_PERMISSIONS_GROUP);
-        foreground.setTitle(R.string.allowed_foreground_header);
-        foreground.setVisible(false);
-        getPreferenceScreen().addPreference(foreground);
-
-        PreferenceGroup denied = new PreferenceCategory(getContext());
-        denied.setKey(KEY_DENIED_PERMISSIONS_GROUP);
-        denied.setTitle(R.string.denied_header);
-        denied.setVisible(false);
-        getPreferenceScreen().addPreference(denied);
+    private void updatePreferenceSummary(Preference preference,
+            Pair<String, Integer> summaryTimestamp) {
+        String summary = mViewModel.getPreferenceSummary(getResources(), summaryTimestamp);
+        if (!summary.isEmpty()) {
+            preference.setSummary(summary);
+        }
     }
 
     @Override
-    public void onPermissionsLoaded(PermissionApps permissionApps) {
-        Context context = getPreferenceManager().getContext();
-
-        if (context == null || getActivity() == null) {
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void onPermissionUsagesChanged() {
+        if (mPermissionUsages.getUsages().isEmpty()) {
+            return;
+        }
+        if (getContext() == null) {
+            // Async result has come in after our context is gone.
             return;
         }
 
-        PreferenceCategory allowed = findPreference(KEY_ALLOWED_PERMISSIONS_GROUP);
-        PreferenceCategory allowedForeground = findPreference(
-                KEY_ALLOWED_FOREGROUND_PERMISSIONS_GROUP);
-        PreferenceCategory denied = findPreference(KEY_DENIED_PERMISSIONS_GROUP);
+        mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages());
+        onPackagesLoaded(mViewModel.getCategorizedAppsLiveData().getValue());
+    }
 
-        allowed.setOrderingAsAdded(true);
-        allowedForeground.setOrderingAsAdded(true);
-        denied.setOrderingAsAdded(true);
+    private int comparePreference(Preference lhs, Preference rhs) {
+        return mViewModel.comparePreference(mCollator, lhs, rhs);
+    }
 
-        Map<String, Preference> existingPrefs = new ArrayMap<>();
-        int numPreferences = allowed.getPreferenceCount();
-        for (int i = 0; i < numPreferences; i++) {
-            Preference preference = allowed.getPreference(i);
-            existingPrefs.put(preference.getKey(), preference);
-        }
-        allowed.removeAll();
-        numPreferences = allowedForeground.getPreferenceCount();
-        for (int i = 0; i < numPreferences; i++) {
-            Preference preference = allowedForeground.getPreference(i);
-            existingPrefs.put(preference.getKey(), preference);
-        }
-        allowedForeground.removeAll();
-        numPreferences = denied.getPreferenceCount();
-        for (int i = 0; i < numPreferences; i++) {
-            Preference preference = denied.getPreference(i);
-            existingPrefs.put(preference.getKey(), preference);
-        }
-        denied.removeAll();
-
-        mHasSystemApps = false;
-        boolean hasPermissionWithBackgroundMode = false;
-
-        ArrayList<PermissionApps.PermissionApp> sortedApps = new ArrayList<>(
-                permissionApps.getApps());
-        sortedApps.sort((x, y) -> {
-            int result = mCollator.compare(x.getLabel(), y.getLabel());
-            if (result == 0) {
-                result = x.getUid() - y.getUid();
-            }
-            return result;
-        });
-
-        for (int i = 0; i < sortedApps.size(); i++) {
-            PermissionApps.PermissionApp app = sortedApps.get(i);
-            AppPermissionGroup group = app.getPermissionGroup();
-
-            hasPermissionWithBackgroundMode =
-                    hasPermissionWithBackgroundMode || group.hasPermissionWithBackgroundMode();
-
-            if (!Utils.shouldShowPermission(getContext(), group)) {
-                continue;
-            }
-
-            if (!app.getAppInfo().enabled) {
-                continue;
-            }
-
-            String key = app.getKey();
-            Preference existingPref = existingPrefs.get(key);
-            if (existingPref != null) {
-                // Without this, existing preferences remember their old order.
-                existingPref.setOrder(Preference.DEFAULT_ORDER);
-            }
-
-            boolean isSystemApp = !Utils.isGroupOrBgGroupUserSensitive(group);
-
-            if (isSystemApp) {
-                mHasSystemApps = true;
-            }
-
-            if (isSystemApp && !mShowSystem) {
-                continue;
-            }
-
-            PreferenceCategory category;
-            if (group.areRuntimePermissionsGranted()) {
-                if (!group.hasPermissionWithBackgroundMode()
-                        || (group.getBackgroundPermissions() != null
-                        && group.getBackgroundPermissions().areRuntimePermissionsGranted())) {
-                    category = allowed;
-                } else {
-                    category = allowedForeground;
-                }
-            } else {
-                category = denied;
-            }
-
-            if (existingPref != null) {
-                category.addPreference(existingPref);
-                continue;
-            }
-
-            PermissionControlPreference pref = new PermissionControlPreference(context, group,
-                    PermissionAppsFragment.class.getName());
-            pref.setKey(key);
-            pref.setIcon(app.getIcon());
-            pref.setTitle(Utils.getFullAppLabel(app.getAppInfo(), context));
-            pref.setEllipsizeEnd();
-            pref.useSmallerIcon();
-            category.addPreference(pref);
-        }
-
-        if (hasPermissionWithBackgroundMode) {
-            allowed.setTitle(R.string.allowed_always_header);
-        }
-
-        if (allowed.getPreferenceCount() == 0) {
-            Preference empty = new Preference(context);
-            empty.setTitle(R.string.no_apps_allowed);
-            empty.setSelectable(false);
-            allowed.addPreference(empty);
-        }
-        allowed.setVisible(true);
-
-        allowedForeground.setVisible(allowedForeground.getPreferenceCount() > 0);
-
-        if (denied.getPreferenceCount() == 0) {
-            Preference empty = new Preference(context);
-            empty.setTitle(R.string.no_apps_denied);
-            empty.setSelectable(false);
-            denied.addPreference(empty);
-        }
-        denied.setVisible(true);
-
-        setShowSystemAppsToggle();
-        setLoading(false);
+    private void logPermissionAppsFragmentCreated(String packageName, UserHandle user, long viewId,
+            boolean isAllowed, boolean isAllowedForeground, boolean isDenied) {
+        long sessionId = getArguments().getLong(EXTRA_SESSION_ID, 0);
+        mViewModel.logPermissionAppsFragmentCreated(packageName, user, viewId, isAllowed,
+                isAllowedForeground, isDenied, sessionId, getActivity().getApplication(),
+                mPermGroupName, LOG_TAG);
     }
 }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoPermissionsUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoPermissionsUtils.java
index 075869c..16544fc 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoPermissionsUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoPermissionsUtils.java
@@ -59,4 +59,14 @@
         preference.setTitle(Utils.getFullAppLabel(appInfo, context));
         return preference;
     }
+
+    /** Creates a {@link Preference} which shows the app icon and app name. */
+    public static Preference createHeaderPreference(Context context, Drawable packageIcon,
+            String packageName, String packageLabel) {
+        Preference preference = new Preference(context);
+        preference.setIcon(packageIcon);
+        preference.setKey(packageName);
+        preference.setTitle(packageLabel);
+        return preference;
+    }
 }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoUnusedAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoUnusedAppsFragment.kt
index 2a62d22..a700668 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoUnusedAppsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoUnusedAppsFragment.kt
@@ -20,10 +20,12 @@
 import android.os.Bundle
 import android.os.UserHandle
 import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
 import com.android.permissioncontroller.R
 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment
 import com.android.permissioncontroller.hibernation.isHibernationEnabled
 import com.android.permissioncontroller.permission.ui.UnusedAppsFragment
+import com.android.permissioncontroller.permission.ui.UnusedAppsFragment.Companion.INFO_MSG_CATEGORY
 import com.android.car.ui.utils.ViewUtils
 import com.android.car.ui.utils.ViewUtils.LazyLayoutView
 
@@ -34,6 +36,8 @@
     UnusedAppsFragment.Parent<AutoUnusedAppsPreference> {
 
     companion object {
+        private const val UNUSED_PREFERENCE_KEY = "unused_pref_row_key"
+
         /** Create a new instance of this fragment.  */
         @JvmStatic
         fun newInstance(): AutoUnusedAppsFragment {
@@ -61,8 +65,8 @@
 
         // initially focus on focus parking view and then shift focus to recyclerview once it has
         // loaded
-        ViewUtils.hideFocus(getListView().getRootView())
-        val lazyLayoutView = getListView() as LazyLayoutView
+        ViewUtils.hideFocus(getCarUiRecyclerView().getView().getRootView())
+        val lazyLayoutView = getCarUiRecyclerView() as LazyLayoutView
         ViewUtils.initFocus(lazyLayoutView)
     }
 
@@ -97,4 +101,25 @@
     override fun setTitle(title: CharSequence) {
         headerLabel = title
     }
+
+    override fun setEmptyState(empty: Boolean) {
+        val infoMsgCategory =
+                preferenceScreen.findPreference<PreferenceCategory>(INFO_MSG_CATEGORY)!!
+        val noUnusedAppsPreference: Preference? =
+                infoMsgCategory.findPreference<Preference>(UNUSED_PREFERENCE_KEY)
+        if (empty && noUnusedAppsPreference == null) {
+            infoMsgCategory.addPreference(createNoUnusedAppsPreference())
+        } else if (noUnusedAppsPreference != null) {
+            noUnusedAppsPreference.setVisible(empty)
+        }
+    }
+
+    private fun createNoUnusedAppsPreference(): Preference {
+        val preference = Preference(context)
+        preference.title = getString(R.string.zero_unused_apps)
+        preference.key = UNUSED_PREFERENCE_KEY
+        preference.isSelectable = false
+        preference.order = 0
+        return preference
+    }
 }
\ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
index 9023a34..a66b15f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
@@ -160,6 +160,7 @@
         }
 
         CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setSecure(true);
         item.setTitle(mContext.getString(stringId));
         item.setOnItemClickedListener(i -> {
             mDialog.setOnDismissListener(null);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
index 999ca59..bb0390f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
@@ -24,10 +24,6 @@
 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED;
 import static com.android.permissioncontroller.hibernation.HibernationPolicyKt.isHibernationEnabled;
 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
-import static com.android.permissioncontroller.permission.utils.Utils.LAST_24H_CONTENT_PROVIDER;
-import static com.android.permissioncontroller.permission.utils.Utils.LAST_24H_SENSOR_TODAY;
-import static com.android.permissioncontroller.permission.utils.Utils.LAST_24H_SENSOR_YESTERDAY;
-import static com.android.permissioncontroller.permission.utils.Utils.NOT_IN_LAST_24H;
 
 import static java.util.concurrent.TimeUnit.DAYS;
 
@@ -68,9 +64,9 @@
 import com.android.permissioncontroller.PermissionControllerStatsLog;
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.permission.model.AppPermissionUsage;
+import com.android.permissioncontroller.permission.model.PermissionUsages;
 import com.android.permissioncontroller.permission.model.livedatatypes.HibernationSettingState;
 import com.android.permissioncontroller.permission.ui.Category;
-import com.android.permissioncontroller.permission.ui.handheld.dashboard.PermissionUsages;
 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel;
 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel.GroupUiInfo;
 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory;
@@ -106,7 +102,6 @@
     private static final String ASSISTANT_MIC_CATEGORY_KEY = "_ASSISTANT_MIC_KEY";
     private static final String ASSISTANT_MIC_SWITCH_KEY = "_ASSISTANT_MIC_SWITCH_KEY";
     private static final String ASSISTANT_MIC_SUMMARY_KEY = "_ASSISTANT_MIC_SUMMARY_KEY";
-    private static final int AGGREGATE_DATA_FILTER_BEGIN_DAYS = 1;
 
     static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
 
@@ -187,7 +182,8 @@
             mPermissionUsages = new PermissionUsages(context);
 
             long filterTimeBeginMillis = Math.max(System.currentTimeMillis()
-                            - DAYS.toMillis(AGGREGATE_DATA_FILTER_BEGIN_DAYS),
+                            - DAYS.toMillis(
+                    AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS),
                     Instant.EPOCH.toEpochMilli());
             mPermissionUsages.load(null, null, filterTimeBeginMillis, Long.MAX_VALUE,
                     PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(),
@@ -272,39 +268,6 @@
         }
     }
 
-    private void extractGroupUsageLastAccessTime(Map<String, Long> accessTime) {
-        accessTime.clear();
-        long filterTimeBeginMillis = Math.max(System.currentTimeMillis()
-                - DAYS.toMillis(AGGREGATE_DATA_FILTER_BEGIN_DAYS), Instant.EPOCH.toEpochMilli());
-
-        int numApps = mAppPermissionUsages.size();
-        for (int appIndex = 0; appIndex < numApps; appIndex++) {
-            AppPermissionUsage appUsage = mAppPermissionUsages.get(appIndex);
-            if (!appUsage.getPackageName().equals(mPackageName)) {
-                continue;
-            }
-
-            List<AppPermissionUsage.GroupUsage> appGroups = appUsage.getGroupUsages();
-            int numGroups = appGroups.size();
-            for (int groupIndex = 0; groupIndex < numGroups; groupIndex++) {
-                AppPermissionUsage.GroupUsage groupUsage = appGroups.get(groupIndex);
-                long lastAccessTime = groupUsage.getLastAccessTime();
-                String groupName = groupUsage.getGroup().getName();
-                if (lastAccessTime == 0 || lastAccessTime < filterTimeBeginMillis) {
-                    continue;
-                }
-
-                // We might have another AppPermissionUsage entry that's of the same packageName
-                // but with a different uid. In that case, we want to grab the max lastAccessTime
-                // as the last usage to show.
-                lastAccessTime = Math.max(
-                        accessTime.getOrDefault(groupName, Instant.EPOCH.toEpochMilli()),
-                        lastAccessTime);
-                accessTime.put(groupName, lastAccessTime);
-            }
-        }
-    }
-
     private void updatePreferences(Map<Category, List<GroupUiInfo>> groupMap) {
         if (groupMap == null) {
             return;
@@ -328,7 +291,8 @@
         }
 
         Map<String, Long> groupUsageLastAccessTime = new HashMap<>();
-        extractGroupUsageLastAccessTime(groupUsageLastAccessTime);
+        mViewModel.extractGroupUsageLastAccessTime(groupUsageLastAccessTime, mAppPermissionUsages,
+                mPackageName);
 
         findPreference(Category.ALLOWED_FOREGROUND.getCategoryName()).setVisible(false);
 
@@ -367,100 +331,10 @@
                 preference.setTitle(KotlinUtils.INSTANCE.getPermGroupLabel(context, groupName));
                 preference.setIcon(KotlinUtils.INSTANCE.getPermGroupIcon(context, groupName));
                 preference.setKey(groupName);
-                switch (groupInfo.getSubtitle()) {
-                    case BACKGROUND:
-                        switch (lastAccessType) {
-                            case LAST_24H_CONTENT_PROVIDER:
-                                preference.setSummary(
-                                        R.string.app_perms_content_provider_background);
-                                break;
-                            case LAST_24H_SENSOR_TODAY:
-                                preference.setSummary(
-                                        getString(
-                                                R.string.app_perms_24h_access_background,
-                                                summaryTimestamp.getFirst()));
-                                break;
-                            case LAST_24H_SENSOR_YESTERDAY:
-                                preference.setSummary(getString(
-                                        R.string.app_perms_24h_access_yest_background,
-                                        summaryTimestamp.getFirst()));
-                                break;
-                            case NOT_IN_LAST_24H:
-                            default:
-                                preference.setSummary(
-                                        R.string.permission_subtitle_background);
-                        }
-
-                        break;
-                    case MEDIA_ONLY:
-                        switch (lastAccessType) {
-                            case LAST_24H_CONTENT_PROVIDER:
-                                preference.setSummary(
-                                        R.string.app_perms_content_provider_media_only);
-                                break;
-                            case LAST_24H_SENSOR_TODAY:
-                                preference.setSummary(
-                                        getString(
-                                                R.string.app_perms_24h_access_media_only,
-                                                summaryTimestamp.getFirst()));
-                                break;
-                            case LAST_24H_SENSOR_YESTERDAY:
-                                preference.setSummary(
-                                        getString(
-                                                R.string.app_perms_24h_access_yest_media_only,
-                                                summaryTimestamp.getFirst()));
-                                break;
-                            case NOT_IN_LAST_24H:
-                            default:
-                                preference.setSummary(R.string.permission_subtitle_media_only);
-                        }
-
-                        break;
-                    case ALL_FILES:
-                        switch (lastAccessType) {
-                            case LAST_24H_CONTENT_PROVIDER:
-                                preference.setSummary(
-                                        R.string.app_perms_content_provider_all_files);
-                                break;
-                            case LAST_24H_SENSOR_TODAY:
-                                preference.setSummary(
-                                        getString(
-                                                R.string.app_perms_24h_access_all_files,
-                                                summaryTimestamp.getFirst()));
-                                break;
-                            case LAST_24H_SENSOR_YESTERDAY:
-                                preference.setSummary(
-                                        getString(
-                                                R.string.app_perms_24h_access_yest_all_files,
-                                                summaryTimestamp.getFirst()));
-                                break;
-                            case NOT_IN_LAST_24H:
-                            default:
-                                preference.setSummary(R.string.permission_subtitle_all_files);
-                        }
-
-                        break;
-                    case FOREGROUND_ONLY:
-                        // We don't want to note the foreground access
-                    default:
-                        switch (lastAccessType) {
-                            case LAST_24H_CONTENT_PROVIDER:
-                                preference.setSummary(
-                                        R.string.app_perms_content_provider);
-                                break;
-                            case LAST_24H_SENSOR_TODAY:
-                                preference.setSummary(
-                                        getString(R.string.app_perms_24h_access,
-                                                summaryTimestamp.getFirst()));
-                                break;
-                            case LAST_24H_SENSOR_YESTERDAY:
-                                preference.setSummary(
-                                        getString(R.string.app_perms_24h_access_yest,
-                                                summaryTimestamp.getFirst()));
-                                break;
-                            case NOT_IN_LAST_24H:
-                            default:
-                        }
+                String summary = mViewModel.getPreferenceSummary(groupInfo, context,
+                        groupUsageLastAccessTime.get(groupName));
+                if (!summary.isEmpty()) {
+                    preference.setSummary(summary);
                 }
                 // Add an info icon if the package handles ACTION_VIEW_PERMISSION_USAGE.
                 PackageManager packageManager = requireActivity().getPackageManager();
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/HandheldUnusedAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/HandheldUnusedAppsFragment.kt
index 55686cf..eaf90de 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/HandheldUnusedAppsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/HandheldUnusedAppsFragment.kt
@@ -22,9 +22,11 @@
 import android.os.UserHandle
 import android.view.MenuItem
 import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
 import com.android.permissioncontroller.R
 import com.android.permissioncontroller.hibernation.isHibernationEnabled
 import com.android.permissioncontroller.permission.ui.UnusedAppsFragment
+import com.android.permissioncontroller.permission.ui.UnusedAppsFragment.Companion.INFO_MSG_CATEGORY
 
 /**
  * Handheld wrapper, with customizations, around [UnusedAppsFragment].
@@ -109,4 +111,10 @@
     override fun setTitle(title: CharSequence) {
         requireActivity().setTitle(title)
     }
+
+    override fun setEmptyState(empty: Boolean) {
+        val infoMsgCategory =
+                preferenceScreen.findPreference<PreferenceCategory>(INFO_MSG_CATEGORY)!!
+        infoMsgCategory.isVisible = !empty
+    }
 }
\ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
index 4491557..b28f490 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
@@ -17,23 +17,12 @@
 
 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED;
-import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED;
 import static com.android.permissioncontroller.permission.ui.Category.ALLOWED;
 import static com.android.permissioncontroller.permission.ui.Category.ALLOWED_FOREGROUND;
 import static com.android.permissioncontroller.permission.ui.Category.ASK;
 import static com.android.permissioncontroller.permission.ui.Category.DENIED;
 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
 import static com.android.permissioncontroller.permission.ui.handheld.dashboard.UtilsKt.shouldShowPermissionsDashboard;
-import static com.android.permissioncontroller.permission.utils.Utils.LAST_24H_CONTENT_PROVIDER;
-import static com.android.permissioncontroller.permission.utils.Utils.LAST_24H_SENSOR_TODAY;
-import static com.android.permissioncontroller.permission.utils.Utils.LAST_24H_SENSOR_YESTERDAY;
-import static com.android.permissioncontroller.permission.utils.Utils.NOT_IN_LAST_24H;
-
-import static java.util.concurrent.TimeUnit.DAYS;
 
 import android.Manifest;
 import android.app.ActionBar;
@@ -46,7 +35,6 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -59,12 +47,11 @@
 import androidx.preference.PreferenceCategory;
 
 import com.android.modules.utils.build.SdkLevel;
-import com.android.permissioncontroller.PermissionControllerStatsLog;
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.permission.model.AppPermissionUsage;
+import com.android.permissioncontroller.permission.model.PermissionUsages;
 import com.android.permissioncontroller.permission.ui.Category;
 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
-import com.android.permissioncontroller.permission.ui.handheld.dashboard.PermissionUsages;
 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel;
 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModelFactory;
 import com.android.permissioncontroller.permission.utils.KotlinUtils;
@@ -73,9 +60,7 @@
 import com.android.settingslib.utils.applications.AppUtils;
 
 import java.text.Collator;
-import java.time.Instant;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
@@ -98,7 +83,6 @@
     private static final String STORAGE_ALLOWED_FULL = "allowed_storage_full";
     private static final String STORAGE_ALLOWED_SCOPED = "allowed_storage_scoped";
     private static final int SHOW_LOAD_DELAY_MS = 200;
-    private static final int AGGREGATE_DATA_FILTER_BEGIN_DAYS = 1;
 
     private static final int MENU_PERMISSION_USAGE = MENU_HIDE_SYSTEM + 1;
 
@@ -167,9 +151,7 @@
             Context context = getPreferenceManager().getContext();
             mPermissionUsages = new PermissionUsages(context);
 
-            long filterTimeBeginMillis = Math.max(System.currentTimeMillis()
-                            - DAYS.toMillis(AGGREGATE_DATA_FILTER_BEGIN_DAYS),
-                    Instant.EPOCH.toEpochMilli());
+            long filterTimeBeginMillis = mViewModel.getFilterTimeBeginMillis();
             mPermissionUsages.load(null, null, filterTimeBeginMillis, Long.MAX_VALUE,
                     PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(),
                     false, false, this, false);
@@ -309,8 +291,8 @@
         }
 
         // A mapping of user + packageName to their last access timestamps for the permission group.
-        Map<String, Long> groupUsageLastAccessTime = new HashMap<>();
-        extractGroupUsageLastAccessTime(groupUsageLastAccessTime);
+        Map<String, Long> groupUsageLastAccessTime =
+                mViewModel.extractGroupUsageLastAccessTime(mAppPermissionUsages);
 
         for (Category grantCategory : categories.keySet()) {
             List<Pair<String, UserHandle>> packages = categories.get(grantCategory);
@@ -422,57 +404,12 @@
 
     private void updatePreferenceSummary(Preference preference,
             Pair<String, Integer> summaryTimestamp) {
-        @Utils.AppPermsLastAccessType int lastAccessType = summaryTimestamp.getSecond();
-
-        switch (lastAccessType) {
-            case LAST_24H_CONTENT_PROVIDER:
-                preference.setSummary(
-                        R.string.app_perms_content_provider);
-                break;
-            case LAST_24H_SENSOR_TODAY:
-                preference.setSummary(
-                        getString(R.string.app_perms_24h_access,
-                                summaryTimestamp.getFirst()));
-                break;
-            case LAST_24H_SENSOR_YESTERDAY:
-                preference.setSummary(
-                        getString(R.string.app_perms_24h_access_yest,
-                                summaryTimestamp.getFirst()));
-                break;
-            case NOT_IN_LAST_24H:
-            default:
+        String summary = mViewModel.getPreferenceSummary(getResources(), summaryTimestamp);
+        if (!summary.isEmpty()) {
+            preference.setSummary(summary);
         }
     }
 
-    private void extractGroupUsageLastAccessTime(Map<String, Long> accessTime) {
-        accessTime.clear();
-        long filterTimeBeginMillis = Math.max(System.currentTimeMillis()
-                - DAYS.toMillis(AGGREGATE_DATA_FILTER_BEGIN_DAYS), Instant.EPOCH.toEpochMilli());
-
-        int numApps = mAppPermissionUsages.size();
-        for (int appIndex = 0; appIndex < numApps; appIndex++) {
-            AppPermissionUsage appUsage = mAppPermissionUsages.get(appIndex);
-            String packageName = appUsage.getPackageName();
-
-            List<AppPermissionUsage.GroupUsage> appGroups = appUsage.getGroupUsages();
-            int numGroups = appGroups.size();
-            for (int groupIndex = 0; groupIndex < numGroups; groupIndex++) {
-                AppPermissionUsage.GroupUsage groupUsage = appGroups.get(groupIndex);
-                String groupName = groupUsage.getGroup().getName();
-                if (!mPermGroupName.equals(groupName)) {
-                    continue;
-                }
-
-                long lastAccessTime = groupUsage.getLastAccessTime();
-                if (lastAccessTime == 0 || lastAccessTime < filterTimeBeginMillis) {
-                    continue;
-                }
-
-                String key = groupUsage.getGroup().getUser() + packageName;
-                accessTime.put(key, lastAccessTime);
-            }
-        }
-    }
 
     private int comparePreference(Preference lhs, Preference rhs) {
         int result = mCollator.compare(lhs.getTitle().toString(),
@@ -486,27 +423,8 @@
     private void logPermissionAppsFragmentCreated(String packageName, UserHandle user, long viewId,
             boolean isAllowed, boolean isAllowedForeground, boolean isDenied) {
         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, 0);
-
-        int category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED;
-        if (isAllowed) {
-            category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
-        } else if (isAllowedForeground) {
-            category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
-        } else if (isDenied) {
-            category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED;
-        }
-
-        Integer uid = KotlinUtils.INSTANCE.getPackageUid(getActivity().getApplication(),
-                packageName, user);
-        if (uid == null) {
-            return;
-        }
-
-        PermissionControllerStatsLog.write(PERMISSION_APPS_FRAGMENT_VIEWED, sessionId, viewId,
-                mPermGroupName, uid, packageName, category);
-        Log.v(LOG_TAG, "PermissionAppsFragment created with sessionId=" + sessionId
-                + " permissionGroupName=" + mPermGroupName + " appUid="
-                + uid + " packageName=" + packageName
-                + " category=" + category);
+        mViewModel.logPermissionAppsFragmentCreated(packageName, user, viewId, isAllowed,
+                isAllowedForeground, isDenied, sessionId, getActivity().getApplication(),
+                mPermGroupName, LOG_TAG);
     }
 }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionDetailsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionDetailsFragment.java
index 05e7292..9ab0dc4 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionDetailsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionDetailsFragment.java
@@ -58,6 +58,7 @@
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
 import com.android.permissioncontroller.permission.model.AppPermissionUsage;
+import com.android.permissioncontroller.permission.model.PermissionUsages;
 import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
 import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionHistoryPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionHistoryPreference.java
index e7fe255..c7beffa 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionHistoryPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionHistoryPreference.java
@@ -247,6 +247,7 @@
         viewUsageIntent.putExtra(Intent.EXTRA_START_TIME,
                 mAccessTimeList.get(mAccessTimeList.size() - 1));
         viewUsageIntent.putExtra(Intent.EXTRA_END_TIME, mAccessTimeList.get(0));
+        viewUsageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
         PackageManager packageManager = mContext.getPackageManager();
         ResolveInfo resolveInfo = packageManager.resolveActivity(viewUsageIntent,
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionUsageV2Fragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionUsageV2Fragment.java
index caeca2d..acc39c2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionUsageV2Fragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/dashboard/PermissionUsageV2Fragment.java
@@ -52,6 +52,7 @@
 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
 import com.android.permissioncontroller.permission.model.AppPermissionUsage;
 import com.android.permissioncontroller.permission.model.AppPermissionUsage.GroupUsage;
+import com.android.permissioncontroller.permission.model.PermissionUsages;
 import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
 import com.android.permissioncontroller.permission.ui.handheld.PermissionUsageV2ControlPreference;
 import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/AppPermissionActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/AppPermissionActivity.java
index fa1ce2f..6386e51 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/AppPermissionActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/AppPermissionActivity.java
@@ -19,6 +19,9 @@
 import static android.content.Intent.ACTION_MANAGE_APP_PERMISSION;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
+import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
+
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionGroupInfo;
@@ -42,6 +45,7 @@
 import com.android.permissioncontroller.permission.utils.Utils;
 
 import java.util.List;
+import java.util.Random;
 
 /**
  * Manage a single permission of a single app.
@@ -146,8 +150,13 @@
         if (DeviceUtils.isAuto(this)) {
             Fragment androidXFragment;
 
+            long sessionId = getIntent().getLongExtra(EXTRA_SESSION_ID, INVALID_SESSION_ID);
+            while (sessionId == INVALID_SESSION_ID) {
+                sessionId = new Random().nextLong();
+            }
+
             androidXFragment = AutoAppPermissionFragment.newInstance(packageName, permissionName,
-                    groupName, userHandle);
+                    groupName, userHandle, sessionId);
 
             getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
                     androidXFragment).commit();
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
index afedeca..0aaf99b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
@@ -21,6 +21,8 @@
 import android.app.AppOpsManager.MODE_ALLOWED
 import android.app.AppOpsManager.MODE_IGNORED
 import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+import android.apphibernation.AppHibernationManager
+import android.content.Context
 import android.os.Bundle
 import android.os.UserHandle
 import android.util.Log
@@ -34,6 +36,7 @@
 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_DISABLED
 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED
 import com.android.permissioncontroller.R
+import com.android.permissioncontroller.hibernation.isHibernationEnabled
 import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
 import com.android.permissioncontroller.permission.data.HibernationSettingStateLiveData
@@ -42,13 +45,18 @@
 import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
 import com.android.permissioncontroller.permission.data.get
+import com.android.permissioncontroller.permission.model.AppPermissionUsage
 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
 import com.android.permissioncontroller.permission.ui.Category
 import com.android.permissioncontroller.permission.utils.IPC
 import com.android.permissioncontroller.permission.utils.Utils
+import com.android.permissioncontroller.permission.utils.Utils.AppPermsLastAccessType
 import com.android.permissioncontroller.permission.utils.navigateSafe
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
+import java.time.Instant
+import java.util.concurrent.TimeUnit
+import kotlin.math.max
 
 /**
  * ViewModel for the AppPermissionGroupsFragment. Has a liveData with the UI information for all
@@ -64,6 +72,7 @@
 ) : ViewModel() {
 
     companion object {
+        const val AGGREGATE_DATA_FILTER_BEGIN_DAYS = 1
         val LOG_TAG: String = AppPermissionGroupsViewModel::class.java.simpleName
     }
 
@@ -209,6 +218,11 @@
                     MODE_IGNORED
                 }
                 aom.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, mode)
+                if (isHibernationEnabled() && !enabled) {
+                    val ahm = app.getSystemService(AppHibernationManager::class.java)!!
+                    ahm.setHibernatingForUser(packageName, false)
+                    ahm.setHibernatingGlobally(packageName, false)
+                }
             }
         }
     }
@@ -220,6 +234,124 @@
     fun showAllPermissions(fragment: Fragment, args: Bundle) {
         fragment.findNavController().navigateSafe(R.id.perm_groups_to_all_perms, args)
     }
+
+    // This method should be consolidated with
+    // PermissionAppsViewModel#extractGroupUsageLastAccessTime
+    fun extractGroupUsageLastAccessTime(
+        accessTime: MutableMap<String, Long>,
+        appPermissionUsages: List<AppPermissionUsage>,
+        packageName: String
+    ) {
+        accessTime.clear()
+        val filterTimeBeginMillis = max(System.currentTimeMillis() -
+                TimeUnit.DAYS.toMillis(AGGREGATE_DATA_FILTER_BEGIN_DAYS.toLong()),
+                Instant.EPOCH.toEpochMilli())
+        val numApps: Int = appPermissionUsages.size
+        for (appIndex in 0 until numApps) {
+            val appUsage: AppPermissionUsage = appPermissionUsages[appIndex]
+            if (appUsage.packageName != packageName) {
+                continue
+            }
+            val appGroups = appUsage.groupUsages
+            val numGroups = appGroups.size
+            for (groupIndex in 0 until numGroups) {
+                val groupUsage = appGroups[groupIndex]
+                var lastAccessTime = groupUsage.lastAccessTime
+                val groupName = groupUsage.group.name
+                if (lastAccessTime == 0L || lastAccessTime < filterTimeBeginMillis) {
+                    continue
+                }
+
+                // We might have another AppPermissionUsage entry that's of the same packageName
+                // but with a different uid. In that case, we want to grab the max lastAccessTime
+                // as the last usage to show.
+                lastAccessTime = Math.max(
+                        accessTime.getOrDefault(groupName, Instant.EPOCH.toEpochMilli()),
+                        lastAccessTime)
+                accessTime[groupName] = lastAccessTime
+            }
+        }
+    }
+
+    fun getPreferenceSummary(groupInfo: GroupUiInfo, context: Context, lastAccessTime: Long?):
+            String {
+        val summaryTimestamp = Utils
+                .getPermissionLastAccessSummaryTimestamp(
+                        lastAccessTime, context, groupInfo.groupName)
+        @AppPermsLastAccessType val lastAccessType: Int = summaryTimestamp.second
+
+        return when (groupInfo.subtitle) {
+            PermSubtitle.BACKGROUND ->
+                when (lastAccessType) {
+                    Utils.LAST_24H_CONTENT_PROVIDER -> context.getString(
+                            R.string.app_perms_content_provider_background)
+                    Utils.LAST_24H_SENSOR_TODAY -> context.getString(
+                            R.string.app_perms_24h_access_background,
+                            summaryTimestamp.first)
+                    Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString(
+                            R.string.app_perms_24h_access_yest_background,
+                            summaryTimestamp.first)
+                    Utils.NOT_IN_LAST_24H -> context.getString(
+                            R.string.permission_subtitle_background)
+                    else -> context.getString(
+                            R.string.permission_subtitle_background)
+                }
+            PermSubtitle.MEDIA_ONLY ->
+                when (lastAccessType) {
+                    Utils.LAST_24H_CONTENT_PROVIDER -> context.getString(
+                            R.string.app_perms_content_provider_media_only)
+                    Utils.LAST_24H_SENSOR_TODAY -> context.getString(
+                            R.string.app_perms_24h_access_media_only,
+                            summaryTimestamp.first)
+                    Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString(
+                            R.string.app_perms_24h_access_yest_media_only,
+                            summaryTimestamp.first)
+                    Utils.NOT_IN_LAST_24H -> context.getString(
+                            R.string.permission_subtitle_media_only)
+                    else -> context.getString(R.string.permission_subtitle_media_only)
+                }
+            PermSubtitle.ALL_FILES ->
+                when (lastAccessType) {
+                    Utils.LAST_24H_CONTENT_PROVIDER -> context.getString(
+                            R.string.app_perms_content_provider_all_files)
+                    Utils.LAST_24H_SENSOR_TODAY -> context.getString(
+                            R.string.app_perms_24h_access_all_files,
+                            summaryTimestamp.first)
+                    Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString(
+                            R.string.app_perms_24h_access_yest_all_files,
+                            summaryTimestamp.first)
+                    Utils.NOT_IN_LAST_24H -> context.getString(
+                            R.string.permission_subtitle_all_files)
+                    else -> context.getString(R.string.permission_subtitle_all_files)
+                }
+            PermSubtitle.FOREGROUND_ONLY ->
+                when (lastAccessType) {
+                    Utils.LAST_24H_CONTENT_PROVIDER -> context.getString(
+                            R.string.app_perms_content_provider)
+                    Utils.LAST_24H_SENSOR_TODAY -> context.getString(
+                            R.string.app_perms_24h_access,
+                            summaryTimestamp.first)
+                    Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString(
+                            R.string.app_perms_24h_access_yest,
+                            summaryTimestamp.first)
+                    Utils.NOT_IN_LAST_24H -> ""
+                    else -> ""
+                }
+            else ->
+                when (lastAccessType) {
+                    Utils.LAST_24H_CONTENT_PROVIDER -> context.getString(
+                            R.string.app_perms_content_provider)
+                    Utils.LAST_24H_SENSOR_TODAY -> context.getString(
+                            R.string.app_perms_24h_access,
+                            summaryTimestamp.first)
+                    Utils.LAST_24H_SENSOR_YESTERDAY -> context.getString(
+                            R.string.app_perms_24h_access_yest,
+                            summaryTimestamp.first)
+                    Utils.NOT_IN_LAST_24H -> ""
+                    else -> ""
+                }
+        }
+    }
 }
 
 /**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
index 7fac5ba..6454970 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -417,7 +417,8 @@
 
                 // Show location permission dialogs based on location permissions
                 val locationVisibilities = MutableList(NEXT_LOCATION_DIALOG) { false }
-                if (groupState.group.permGroupName == LOCATION && isLocationAccuracyEnabled()) {
+                if (groupState.group.permGroupName == LOCATION && isLocationAccuracyEnabled() &&
+                        packageInfo.targetSdkVersion >= Build.VERSION_CODES.S) {
                     if (needFgPermissions) {
                         locationVisibilities[LOCATION_ACCURACY_LAYOUT] = true
                         if (fgState != null &&
@@ -433,6 +434,12 @@
                                     buttonVisibilities[ALLOW_FOREGROUND_BUTTON] = false
                                 }
                             } else {
+                                if (!fgState.affectedPermissions.contains(ACCESS_COARSE_LOCATION)) {
+                                    Log.e(LOG_TAG, "ACCESS_FINE_LOCATION must be requested " +
+                                            "with ACCESS_COARSE_LOCATION.")
+                                    value = null
+                                    return
+                                }
                                 if (coarseLocationPerm?.isOneTime == false &&
                                         !coarseLocationPerm.isUserSet &&
                                         !coarseLocationPerm.isUserFixed) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManagePermissionsViewModel.kt
similarity index 96%
rename from PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsViewModel.kt
rename to PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManagePermissionsViewModel.kt
index 8d4e8c2..970569c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManagePermissionsViewModel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.permissioncontroller.permission.ui.television
+package com.android.permissioncontroller.permission.ui.model
 
 import android.app.Application
 import androidx.lifecycle.AndroidViewModel
@@ -23,7 +23,6 @@
 import com.android.permissioncontroller.permission.data.PermGroupsPackagesUiInfoLiveData
 import com.android.permissioncontroller.permission.data.StandardPermGroupNamesLiveData
 import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo
-import com.android.permissioncontroller.permission.ui.model.UsedCustomPermGroupNamesLiveData
 
 /**
  * A [androidx.lifecycle.ViewModel] for [ManagePermissionsFragment] and
@@ -35,6 +34,7 @@
  * [ManagePermissionsViewModel].
  */
 class ManagePermissionsViewModel(app: Application) : AndroidViewModel(app) {
+
     private val standardPermGroupsLiveData: LiveData<List<PermGroupPackagesUiInfo>> =
         MediatorLiveData<List<PermGroupPackagesUiInfo>>().apply {
             addSource(PermGroupsPackagesUiInfoLiveData(app, StandardPermGroupNamesLiveData)) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
index c7a13db..c4473b1 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
@@ -19,20 +19,29 @@
 import android.Manifest
 import android.app.Application
 import android.content.Intent
+import android.content.res.Resources
 import android.os.Bundle
 import android.os.UserHandle
+import android.util.Log
 import androidx.fragment.app.Fragment
 import androidx.lifecycle.AbstractSavedStateViewModelFactory
 import androidx.lifecycle.MediatorLiveData
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
 import androidx.navigation.fragment.findNavController
+import androidx.preference.Preference
 import androidx.savedstate.SavedStateRegistryOwner
+import com.android.permissioncontroller.PermissionControllerStatsLog
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED
 import com.android.permissioncontroller.R
 import com.android.permissioncontroller.permission.data.AllPackageInfosLiveData
 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState
 import com.android.permissioncontroller.permission.data.SinglePermGroupPackagesUiInfoLiveData
+import com.android.permissioncontroller.permission.model.AppPermissionUsage
 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
 import com.android.permissioncontroller.permission.ui.Category
 import com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog
@@ -40,8 +49,14 @@
 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.HAS_SYSTEM_APPS_KEY
 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.SHOULD_SHOW_SYSTEM_KEY
 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.SHOW_ALWAYS_ALLOWED
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageUid
 import com.android.permissioncontroller.permission.utils.LocationUtils
+import com.android.permissioncontroller.permission.utils.Utils
 import com.android.permissioncontroller.permission.utils.navigateSafe
+import java.text.Collator
+import java.time.Instant
+import java.util.concurrent.TimeUnit
+import kotlin.math.max
 
 /**
  * ViewModel for the PermissionAppsFragment. Has a liveData with all of the UI info for each
@@ -59,6 +74,7 @@
 ) : ViewModel() {
 
     companion object {
+        const val AGGREGATE_DATA_FILTER_BEGIN_DAYS = 1
         internal const val SHOULD_SHOW_SYSTEM_KEY = "showSystem"
         internal const val HAS_SYSTEM_APPS_KEY = "hasSystem"
         internal const val SHOW_ALWAYS_ALLOWED = "showAlways"
@@ -240,6 +256,110 @@
 
         fragment.findNavController().navigateSafe(R.id.perm_apps_to_app, args)
     }
+
+    fun getFilterTimeBeginMillis(): Long {
+        return max(System.currentTimeMillis() -
+                TimeUnit.DAYS.toMillis(AGGREGATE_DATA_FILTER_BEGIN_DAYS.toLong()),
+                Instant.EPOCH.toEpochMilli())
+    }
+
+    /**
+     * Return a mapping of user + packageName to their last access timestamps for the permission
+     * group.
+     */
+    fun extractGroupUsageLastAccessTime(appPermissionUsages: List<AppPermissionUsage>):
+            MutableMap<String, Long> {
+        val accessTime: MutableMap<String, Long> = HashMap()
+        val now = System.currentTimeMillis()
+        val filterTimeBeginMillis = max(
+                now - TimeUnit.DAYS.toMillis(AGGREGATE_DATA_FILTER_BEGIN_DAYS.toLong()),
+                Instant.EPOCH.toEpochMilli())
+        val numApps: Int = appPermissionUsages.size
+        for (appIndex in 0 until numApps) {
+            val appUsage: AppPermissionUsage = appPermissionUsages.get(appIndex)
+            val packageName = appUsage.packageName
+            val appGroups = appUsage.groupUsages
+            val numGroups = appGroups.size
+            for (groupIndex in 0 until numGroups) {
+                val groupUsage = appGroups[groupIndex]
+                val groupUsageGroupName = groupUsage.group.name
+                if (groupName != groupUsageGroupName) {
+                    continue
+                }
+                val lastAccessTime = groupUsage.lastAccessTime
+                if (lastAccessTime == 0L || lastAccessTime < filterTimeBeginMillis) {
+                    continue
+                }
+                val key = groupUsage.group.user.toString() + packageName
+                accessTime[key] = lastAccessTime
+            }
+        }
+        return accessTime
+    }
+
+    /**
+     * Return the String preference summary based on the last access time.
+     */
+    fun getPreferenceSummary(res: Resources, summaryTimestamp: Pair<String, Int>): String {
+        return when (summaryTimestamp.second) {
+            Utils.LAST_24H_CONTENT_PROVIDER -> res.getString(
+                    R.string.app_perms_content_provider)
+            Utils.LAST_24H_SENSOR_TODAY -> res.getString(R.string.app_perms_24h_access,
+                    summaryTimestamp.first)
+            Utils.LAST_24H_SENSOR_YESTERDAY -> res.getString(R.string.app_perms_24h_access_yest,
+                    summaryTimestamp.first)
+            else -> ""
+        }
+    }
+
+    /**
+     * Return two preferences to determine their ordering.
+     */
+    fun comparePreference(collator: Collator, lhs: Preference, rhs: Preference): Int {
+        var result: Int = collator.compare(lhs.title.toString(),
+                rhs.title.toString())
+        if (result == 0) {
+            result = lhs.key.compareTo(rhs.key)
+        }
+        return result
+    }
+
+    /**
+     * Log that the fragment was created.
+     */
+    fun logPermissionAppsFragmentCreated(
+        packageName: String,
+        user: UserHandle,
+        viewId: Long,
+        isAllowed: Boolean,
+        isAllowedForeground: Boolean,
+        isDenied: Boolean,
+        sessionId: Long,
+        application: Application,
+        permGroupName: String,
+        tag: String
+    ) {
+        var category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED
+        when {
+            isAllowed -> {
+                category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED
+            }
+            isAllowedForeground -> {
+                category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND
+            }
+            isDenied -> {
+                category = PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED
+            }
+        }
+        val uid = getPackageUid(application,
+                packageName, user) ?: return
+        PermissionControllerStatsLog.write(
+                PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED, sessionId, viewId,
+                permGroupName, uid, packageName, category)
+        Log.v(tag, tag + " created with sessionId=" + sessionId +
+                " permissionGroupName=" + permGroupName + " appUid=" + uid +
+                " packageName=" + packageName + " category=" + category)
+    }
 }
 
 /**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/PermissionGroupPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
similarity index 94%
rename from PermissionController/src/com/android/permissioncontroller/permission/ui/television/PermissionGroupPreference.java
rename to PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
index bb45470..b69fb66 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/PermissionGroupPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.permissioncontroller.permission.ui.television;
+package com.android.permissioncontroller.permission.ui.model;
 
 import android.content.Context;
 import android.content.Intent;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/PermissionGroupPreferenceUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
similarity index 89%
rename from PermissionController/src/com/android/permissioncontroller/permission/ui/television/PermissionGroupPreferenceUtils.java
rename to PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
index a9578ac..49a01ef 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/PermissionGroupPreferenceUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.permissioncontroller.permission.ui.television;
+package com.android.permissioncontroller.permission.ui.model;
 
 import android.content.Context;
 
@@ -27,9 +27,16 @@
 import java.util.List;
 import java.util.Objects;
 
-final class PermissionGroupPreferenceUtils {
+/**
+ * Utils related to displaying permission groups in preferences.
+ */
+public final class PermissionGroupPreferenceUtils {
 
-    static void updateGroupOfPermissionPreferences(Context context, PreferenceGroup preferenceGroup,
+    /**
+     * Update a {@link PreferenceGroup} with the specified permission groups.
+     */
+    public static void updateGroupOfPermissionPreferences(Context context,
+            PreferenceGroup preferenceGroup,
             List<PermGroupPackagesUiInfo> permissionGroups) {
         if (!(permissionGroups instanceof ArrayList)) {
             permissionGroups = new ArrayList<>(permissionGroups);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsFragment.java
index 51af4bc..38a899b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsFragment.java
@@ -31,6 +31,8 @@
 
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo;
+import com.android.permissioncontroller.permission.ui.model.ManagePermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.PermissionGroupPreferenceUtils;
 import com.android.permissioncontroller.permission.utils.Utils;
 
 import java.util.List;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsOtherFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsOtherFragment.java
index 6e13dfa..629cf34 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsOtherFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/ManagePermissionsOtherFragment.java
@@ -32,6 +32,8 @@
 
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo;
+import com.android.permissioncontroller.permission.ui.model.ManagePermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.PermissionGroupPreferenceUtils;
 
 import java.util.List;