Merge "Add an info icon to each permission group in location provider permissions" into sc-dev
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml
index 7d475e0..ba9bb29 100644
--- a/PermissionController/AndroidManifest.xml
+++ b/PermissionController/AndroidManifest.xml
@@ -128,6 +128,7 @@
                 <action android:name="android.intent.action.MANAGE_PERMISSION_APPS" />
                 <action android:name="android.intent.action.MANAGE_PERMISSIONS" />
                 <action android:name="android.intent.action.REVIEW_PERMISSION_USAGE" />
+                <action android:name="android.intent.action.MANAGE_UNUSED_APPS" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
diff --git a/PermissionController/res/layout/permission_history_widget.xml b/PermissionController/res/layout/permission_history_widget.xml
new file mode 100644
index 0000000..8b72b06
--- /dev/null
+++ b/PermissionController/res/layout/permission_history_widget.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:minWidth="56dp"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/permission_history_icon"
+        android:layout_width="@dimen/permission_icon_size"
+        android:layout_height="@dimen/permission_icon_size"/>
+
+    <TextView
+        android:id="@+id/permission_history_time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml
index a11b5be..adfa111 100644
--- a/PermissionController/res/values/strings.xml
+++ b/PermissionController/res/values/strings.xml
@@ -184,6 +184,9 @@
    <!-- [CHAR LIMIT=NONE] Warning message when turning off permission for system apps -->
    <string name="system_warning">If you deny this permission, basic features of your device may no longer function as intended.</string>
 
+    <!-- [CHAR LIMIT=NONE] Warning message when turning off permission for an app managing a companion device -->
+    <string name="cdm_profile_revoke_warning">If you deny this permission, some features of your device managed by this app may no longer function as intended.</string>
+
    <!-- [CHAR LIMIT=NONE] Summary of a permission switch when it's enforced by policy -->
    <string name="permission_summary_enforced_by_policy">Enforced by policy</string>
 
@@ -440,6 +443,9 @@
         <item quantity="other"><xliff:g id="number" example="7">%s</xliff:g> apps</item>
     </plurals>
 
+    <!-- Label for the title of permission history. [CHAR LIMIT=20] -->
+    <string name="permission_history_title">Permission history</string>
+
     <!-- Help URL, permission usage [DO NOT TRANSLATE] -->
     <string name="help_permission_usage" translatable="false"></string>
 
@@ -891,7 +897,7 @@
     <string name="role_call_screening_request_description">No permissions needed</string>
 
     <!-- Description for the watch profile role. [CHAR LIMIT=NONE] -->
-    <string name="role_watch_description"><xliff:g id="app_name" example="Wear">%1$s</xliff:g> will get access to Notifications, Phone, Contacts and Calendar.</string>
+    <string name="role_watch_description"><xliff:g id="app_name" example="Wear">%1$s</xliff:g> will get access to Notifications, Phone, SMS, Contacts and Calendar.</string>
 
     <!-- Subtitle for the application that is the current default application [CHAR LIMIT=30] -->
     <string name="request_role_current_default">Current default</string>
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml
index 48cd17a..b882218 100644
--- a/PermissionController/res/xml/roles.xml
+++ b/PermissionController/res/xml/roles.xml
@@ -283,6 +283,7 @@
             <permission-set name="storage" />
             <permission-set name="microphone" />
             <permission-set name="camera" />
+            <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
         </permissions>
         <app-ops>
             <app-op name="android:write_sms" mode="allowed" />
@@ -345,6 +346,9 @@
                 </intent-filter>
             </activity>
         </required-components>
+        <permissions>
+            <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
+        </permissions>
     </role>
 
     <!---
@@ -440,6 +444,7 @@
         <permissions>
             <permission-set name="storage" />
             <permission name="android.permission.ACCESS_MEDIA_LOCATION" />
+            <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
         </permissions>
         <app-ops>
             <app-op name="android:write_media_images" mode="allowed" />
@@ -452,6 +457,7 @@
         behavior="SystemAutomotiveClusterRoleBehavior"
         defaultHolders="config_systemAutomotiveCluster"
         exclusive="true"
+        minSdkVersion="31"
         systemOnly="true"
         visible="false">
         <permissions>
@@ -467,12 +473,14 @@
         behavior="CompanionDeviceWatchRoleBehavior"
         description="@string/role_watch_description"
         exclusive="false"
+        minSdkVersion="31"
         overrideUserWhenGranting="false"
         systemOnly="false"
         visible="false">
         <permissions>
             <permission-set name="calendar" />
             <permission-set name="phone" />
+            <permission-set name="sms" />
             <permission-set name="contacts" />
         </permissions>
         <app-op-permissions>
@@ -485,6 +493,7 @@
         name="android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"
         defaultHolders="config_systemAutomotiveProjection"
         exclusive="false"
+        minSdkVersion="31"
         systemOnly="true"
         visible="false">
         <permissions>
@@ -505,6 +514,7 @@
         behavior="SystemShellRoleBehavior"
         defaultHolders="config_systemShell"
         exclusive="true"
+        minSdkVersion="31"
         systemOnly="true"
         visible="false">
         <permissions>
@@ -518,6 +528,7 @@
         name="android.app.role.SYSTEM_CONTACTS"
         defaultHolders="config_systemContacts"
         exclusive="true"
+        minSdkVersion="31"
         systemOnly="true"
         visible="false">
         <permissions>
@@ -529,6 +540,7 @@
         name="android.app.role.SYSTEM_SPEECH_RECOGNIZER"
         defaultHolders="config_systemSpeechRecognizer"
         exclusive="true"
+        minSdkVersion="31"
         systemOnly="true"
         visible="false">
         <permissions>
@@ -547,6 +559,7 @@
         name="android.app.role.SYSTEM_WIFI_COEX_MANAGER"
         defaultHolders="config_systemWifiCoexManager"
         exclusive="true"
+        minSdkVersion="31"
         systemOnly="true"
         visible="false">
         <permissions>
@@ -559,6 +572,7 @@
         name="android.app.role.SYSTEM_WELLBEING"
         defaultHolders="config_systemWellbeing"
         exclusive="true"
+        minSdkVersion="31"
         systemOnly="true"
         visible="false" >
         <permissions>
@@ -567,4 +581,16 @@
             <permission name="android.permission.SYSTEM_APPLICATION_OVERLAY"/>
         </permissions>
     </role>
+
+    <role
+        name="android.app.role.SYSTEM_TELEVISION_NOTIFICATION_HANDLER"
+        defaultHolders="config_systemTelevisionNotificationHandler"
+        exclusive="true"
+        minSdkVersion="31"
+        systemOnly="true"
+        visible="false">
+        <permissions>
+            <permission name="android.permission.SYSTEM_APPLICATION_OVERLAY" />
+        </permissions>
+    </role>
 </roles>
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionDetailsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionDetailsFragment.java
new file mode 100644
index 0000000..86518c2
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionDetailsFragment.java
@@ -0,0 +1,342 @@
+/*
+ * 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.debug;
+
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import android.app.ActionBar;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+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.legacy.PermissionApps;
+import com.android.permissioncontroller.permission.ui.handheld.PermissionHistoryPreference;
+import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
+import com.android.permissioncontroller.permission.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The permission details page showing the history/timeline of a permission
+ */
+public class PermissionDetailsFragment extends SettingsWithLargeHeader implements
+        PermissionUsages.PermissionsUsagesChangeCallback {
+    private @Nullable String mFilterGroup;
+    private @Nullable List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>();
+    private @NonNull List<TimeFilterItem> mFilterTimes;
+    private int mFilterTimeIndex;
+    private @NonNull PermissionUsages mPermissionUsages;
+    private boolean mFinishedInitialLoad;
+
+    /**
+     * Construct a new instance of PermissionDetailsFragment
+     */
+    public static @NonNull PermissionDetailsFragment newInstance(@Nullable String groupName,
+            long numMillis) {
+        PermissionDetailsFragment fragment = new PermissionDetailsFragment();
+        Bundle arguments = new Bundle();
+        if (groupName != null) {
+            arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName);
+        }
+        arguments.putLong(Intent.EXTRA_DURATION_MILLIS, numMillis);
+        fragment.setArguments(arguments);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mFinishedInitialLoad = false;
+        initializeTimeFilter();
+        if (mFilterGroup == null) {
+            mFilterGroup = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
+        }
+
+        setHasOptionsMenu(true);
+        ActionBar ab = getActivity().getActionBar();
+        if (ab != null) {
+            ab.setDisplayHomeAsUpEnabled(true);
+        }
+
+        Context context = getPreferenceManager().getContext();
+
+        mPermissionUsages = new PermissionUsages(context);
+        reloadData();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        getActivity().setTitle(R.string.permission_history_title);
+    }
+
+    @Override
+    public void onPermissionUsagesChanged() {
+        if (mPermissionUsages.getUsages().isEmpty()) {
+            return;
+        }
+        mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages());
+
+        // Ensure the group name is valid.
+        if (getGroup(mFilterGroup) == null) {
+            mFilterGroup = null;
+        }
+
+        updateUI();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            getActivity().finish();
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateUI() {
+        if (mAppPermissionUsages.isEmpty() || getActivity() == null) {
+            return;
+        }
+        Context context = getActivity();
+        PreferenceScreen screen = getPreferenceScreen();
+        if (screen == null) {
+            screen = getPreferenceManager().createPreferenceScreen(context);
+            setPreferenceScreen(screen);
+        }
+        screen.removeAll();
+
+        final TimeFilterItem timeFilterItem = mFilterTimes.get(mFilterTimeIndex);
+        long curTime = System.currentTimeMillis();
+        long startTime = Math.max(timeFilterItem == null ? 0 : (curTime - timeFilterItem.getTime()),
+                0);
+
+        AppPermissionGroup group = getGroup(mFilterGroup);
+        Preference permissionPreference = new Preference(context);
+        // TODO: theianchen use KotlinUtils.INSTANCE.getPermGroupLabel()
+        permissionPreference.setTitle(group.getLabel());
+        // TODO: theianchen use KotlinUtils.INSTANCE.getPermGroupLabel()
+        permissionPreference.setIcon(group.getIconResId());
+        screen.addPreference(permissionPreference);
+
+        List<Pair<AppPermissionUsage, Long>> usages = new ArrayList<>();
+        ArrayList<PermissionApps.PermissionApp> permApps = new ArrayList<>();
+        int numApps = mAppPermissionUsages.size();
+        for (int appNum = 0; appNum < numApps; appNum++) {
+            AppPermissionUsage appUsage = mAppPermissionUsages.get(appNum);
+            boolean used = false;
+            List<AppPermissionUsage.GroupUsage> appGroups = appUsage.getGroupUsages();
+            int numGroups = appGroups.size();
+            for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+                AppPermissionUsage.GroupUsage groupUsage = appGroups.get(groupNum);
+                long lastAccessTime = groupUsage.getLastAccessTime();
+
+                if (!groupUsage.hasDiscreteData()) {
+                    continue;
+                }
+                if (lastAccessTime == 0) {
+                    continue;
+                }
+                if (lastAccessTime < startTime) {
+                    continue;
+                }
+
+                used = true;
+                // identify whether the group matches the group for the page
+                if (groupUsage.getGroup().getName().equals(group.getName())) {
+                    usages.add(Pair.create(appUsage, lastAccessTime));
+                }
+            }
+
+            if (used) {
+                permApps.add(appUsage.getApp());
+            }
+
+        }
+
+        usages.sort(PermissionDetailsFragment::compareAppUsageByTime);
+
+        PreferenceCategory category = new PreferenceCategory(context);
+        screen.addPreference(category);
+
+        new PermissionApps.AppDataLoader(context, () -> {
+            final int numUsages = usages.size();
+            for (int usageNum = 0; usageNum < numUsages; usageNum++) {
+                final Pair<AppPermissionUsage, Long> usage = usages.get(usageNum);
+                String accessTime = UtilsKt.getAbsoluteTimeString(context, usage.second);
+
+                PermissionHistoryPreference permissionUsagePreference =
+                        new PermissionHistoryPreference(context, accessTime,
+                                usage.first.getApp().getIcon());
+                permissionUsagePreference.setTitle(usage.first.getApp().getLabel());
+
+                category.addPreference(permissionUsagePreference);
+            }
+
+            setLoading(false, true);
+            mFinishedInitialLoad = true;
+            setProgressBarVisible(false);
+            mPermissionUsages.stopLoader(getActivity().getLoaderManager());
+
+        }).execute(permApps.toArray(new PermissionApps.PermissionApp[permApps.size()]));
+    }
+
+    private static int compareAppUsageByTime(Pair<AppPermissionUsage, Long> x,
+            Pair<AppPermissionUsage, Long> y) {
+        return y.second.compareTo(x.second);
+    }
+
+    /**
+     * Get an AppPermissionGroup that represents the given permission group (and an arbitrary app).
+     *
+     * @param groupName The name of the permission group.
+     *
+     * @return an AppPermissionGroup rerepsenting the given permission group or null if no such
+     * AppPermissionGroup is found.
+     */
+    private @Nullable AppPermissionGroup getGroup(@NonNull String groupName) {
+        List<AppPermissionGroup> groups = getOSPermissionGroups();
+        int numGroups = groups.size();
+        for (int i = 0; i < numGroups; i++) {
+            if (groups.get(i).getName().equals(groupName)) {
+                return groups.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the permission groups declared by the OS.
+     *
+     * TODO: theianchen change the method name to make that clear,
+     * and return a list of string group names, not AppPermissionGroups.
+     * @return a list of the permission groups declared by the OS.
+     */
+    private @NonNull List<AppPermissionGroup> getOSPermissionGroups() {
+        final List<AppPermissionGroup> groups = new ArrayList<>();
+        final Set<String> seenGroups = new ArraySet<>();
+        final int numGroups = mAppPermissionUsages.size();
+        for (int i = 0; i < numGroups; i++) {
+            final AppPermissionUsage appUsage = mAppPermissionUsages.get(i);
+            final List<AppPermissionUsage.GroupUsage> groupUsages = appUsage.getGroupUsages();
+            final int groupUsageCount = groupUsages.size();
+            for (int j = 0; j < groupUsageCount; j++) {
+                final AppPermissionUsage.GroupUsage groupUsage = groupUsages.get(j);
+                if (Utils.isModernPermissionGroup(groupUsage.getGroup().getName())) {
+                    if (seenGroups.add(groupUsage.getGroup().getName())) {
+                        groups.add(groupUsage.getGroup());
+                    }
+                }
+            }
+        }
+        return groups;
+    }
+
+    private void reloadData() {
+        final TimeFilterItem timeFilterItem = mFilterTimes.get(mFilterTimeIndex);
+        final long filterTimeBeginMillis = Math.max(System.currentTimeMillis()
+                - timeFilterItem.getTime(), 0);
+        mPermissionUsages.load(null /*filterPackageName*/, null /*filterPermissionGroups*/,
+                filterTimeBeginMillis, Long.MAX_VALUE, PermissionUsages.USAGE_FLAG_LAST
+                        | PermissionUsages.USAGE_FLAG_HISTORICAL, getActivity().getLoaderManager(),
+                false /*getUiInfo*/, false /*getNonPlatformPermissions*/, this /*callback*/,
+                false /*sync*/);
+        if (mFinishedInitialLoad) {
+            setProgressBarVisible(true);
+        }
+    }
+
+    /**
+     * Initialize the time filter to show the smallest entry greater than the time passed in as an
+     * argument.  If nothing is passed, this simply initializes the possible values.
+     */
+    private void initializeTimeFilter() {
+        Context context = getPreferenceManager().getContext();
+        mFilterTimes = new ArrayList<>();
+        mFilterTimes.add(new TimeFilterItem(Long.MAX_VALUE,
+                context.getString(R.string.permission_usage_any_time)));
+        mFilterTimes.add(new TimeFilterItem(DAYS.toMillis(7),
+                context.getString(R.string.permission_usage_last_7_days)));
+        mFilterTimes.add(new TimeFilterItem(DAYS.toMillis(1),
+                context.getString(R.string.permission_usage_last_day)));
+        mFilterTimes.add(new TimeFilterItem(HOURS.toMillis(1),
+                context.getString(R.string.permission_usage_last_hour)));
+        mFilterTimes.add(new TimeFilterItem(MINUTES.toMillis(15),
+                context.getString(R.string.permission_usage_last_15_minutes)));
+        mFilterTimes.add(new TimeFilterItem(MINUTES.toMillis(1),
+                context.getString(R.string.permission_usage_last_minute)));
+
+        long numMillis = getArguments().getLong(Intent.EXTRA_DURATION_MILLIS);
+        long supremum = Long.MAX_VALUE;
+        int supremumIndex = -1;
+        int numTimes = mFilterTimes.size();
+        for (int i = 0; i < numTimes; i++) {
+            long curTime = mFilterTimes.get(i).getTime();
+            if (curTime >= numMillis && curTime <= supremum) {
+                supremum = curTime;
+                supremumIndex = i;
+            }
+        }
+        if (supremumIndex != -1) {
+            mFilterTimeIndex = supremumIndex;
+        }
+    }
+
+    /**
+     * A class representing a given time, e.g., "in the last hour".
+     */
+    private static class TimeFilterItem {
+        private final long mTime;
+        private final @NonNull String mLabel;
+
+        TimeFilterItem(long time, @NonNull String label) {
+            mTime = time;
+            mLabel = label;
+        }
+
+        /**
+         * Get the time represented by this object in milliseconds.
+         *
+         * @return the time represented by this object.
+         */
+        public long getTime() {
+            return mTime;
+        }
+
+        public @NonNull String getLabel() {
+            return mLabel;
+        }
+    }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionUsageV2Fragment.java b/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionUsageV2Fragment.java
index 40003ee..b4cd3a4 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionUsageV2Fragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionUsageV2Fragment.java
@@ -59,6 +59,7 @@
 import com.android.permissioncontroller.permission.model.AppPermissionUsage.GroupUsage;
 import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
 import com.android.permissioncontroller.permission.ui.handheld.PermissionControlPreference;
+import com.android.permissioncontroller.permission.ui.handheld.PermissionUsageV2ControlPreference;
 import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
 import com.android.permissioncontroller.permission.utils.Utils;
 import com.android.settingslib.HelpUtils;
@@ -409,7 +410,7 @@
                 GroupUsage groupUsage = appGroups.get(groupNum);
                 long lastAccessTime = groupUsage.getLastAccessTime();
 
-                if (groupUsage.getAccessCount() <= 0) {
+                if (!groupUsage.hasDiscreteData()) {
                     continue;
                 }
                 if (lastAccessTime == 0) {
@@ -421,12 +422,10 @@
                 if (lastAccessTime < startTime) {
                     continue;
                 }
+
                 final boolean isSystemApp = !Utils.isGroupOrBgGroupUserSensitive(
                         groupUsage.getGroup());
                 seenSystemApp = seenSystemApp || isSystemApp;
-                if (isSystemApp && !mShowSystem) {
-                    continue;
-                }
 
                 used = true;
                 addGroupUser(groupUsage.getGroup().getName());
@@ -457,7 +456,7 @@
             ArrayList<PermissionApps.PermissionApp> permApps,
             PreferenceCategory category) {
         new PermissionApps.AppDataLoader(context, () -> {
-            Preference permissionUsagePreference = null;
+            PermissionUsageV2ControlPreference permissionUsagePreference = null;
             int permissionUsageAppCount = 0;
             GroupUsage lastGroupUsage = null;
             String lastAccessTimeString = null;
@@ -539,13 +538,17 @@
         }
     }
 
-    private Preference createPermissionUsagePreference(@NonNull Context context,
-            @NonNull AppPermissionGroup appPermissionGroup, @Nullable String summaryString) {
-        Preference permissionUsagePreference = new Preference(context);
+    private PermissionUsageV2ControlPreference createPermissionUsagePreference(
+            @NonNull Context context,
+            @NonNull AppPermissionGroup appPermissionGroup,
+            @Nullable String summaryString) {
+        PermissionUsageV2ControlPreference permissionUsagePreference =
+                new PermissionUsageV2ControlPreference(context);
 
         permissionUsagePreference.setTitle(appPermissionGroup.getLabel()
                 + " " + context.getString(R.string.suffix_permission_usage_preference));
         permissionUsagePreference.setIcon(appPermissionGroup.getIconResId());
+        permissionUsagePreference.setGroup(appPermissionGroup.getName());
         if (summaryString != null) {
             permissionUsagePreference.setSummary(summaryString);
         }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java b/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java
index 4f06132..2999ab0 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java
@@ -39,9 +39,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+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.AppPermissionGroup;
 import com.android.permissioncontroller.permission.model.Permission;
 import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
 import com.android.permissioncontroller.permission.model.legacy.PermissionGroup;
@@ -292,12 +292,13 @@
             if ((mUsageFlags & USAGE_FLAG_HISTORICAL) != 0) {
                 final AtomicReference<HistoricalOps> historicalOpsRef = new AtomicReference<>();
                 final CountDownLatch latch = new CountDownLatch(1);
+
+                // query for discrete timeline data for location, mic and camera
                 final HistoricalOpsRequest request = new HistoricalOpsRequest.Builder(
                         mFilterBeginTimeMillis, mFilterEndTimeMillis)
-                        .setUid(mFilterUid)
-                        .setPackageName(mFilterPackageName)
-                        .setOpNames(new ArrayList<>(opNames))
-                        .setFlags(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+                        .setFlags(AppOpsManager.OP_FLAG_SELF
+                                | AppOpsManager.OP_FLAG_TRUSTED_PROXIED)
+                        .setHistoryFlags(AppOpsManager.HISTORY_FLAG_DISCRETE)
                         .build();
                 appOpsManager.getHistoricalOps(request, Runnable::run,
                         (HistoricalOps ops) -> {
@@ -309,6 +310,7 @@
                 } catch (InterruptedException ignored) { }
 
                 final HistoricalOps historicalOps = historicalOpsRef.get();
+
                 if (historicalOps != null) {
                     final int uidCount = historicalOps.getUidCount();
                     for (int i = 0; i < uidCount; i++) {
@@ -351,6 +353,7 @@
                 final PackageOps lastUsage = lastUsages.get(key);
                 usageBuilder.setLastUsage(lastUsage);
                 final HistoricalPackageOps historicalUsage = historicalUsages.get(key);
+
                 usageBuilder.setHistoricalUsage(historicalUsage);
                 usageBuilder.setRecordingConfiguration(recordingsByUid.get(key.first));
                 usages.add(usageBuilder.build());
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionUsage.java b/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionUsage.java
index d45e9b4..af0e786 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionUsage.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionUsage.java
@@ -179,6 +179,27 @@
             );
         }
 
+        /**
+         * returns whether the usage has discrete data
+         */
+        public boolean hasDiscreteData() {
+            if (mHistoricalUsage == null) {
+                return false;
+            }
+
+            final ArrayList<Permission> permissions = mGroup.getPermissions();
+            final int permissionCount = permissions.size();
+            for (int i = 0; i < permissionCount; i++) {
+                final Permission permission = permissions.get(i);
+                final String opName = permission.getAppOp();
+                final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName);
+                if (historicalOp != null && historicalOp.getDiscreteAccessCount() > 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
         public long getLastAccessDuration() {
             if (mLastUsage == null) {
                 return 0;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index 81b8788..e3a23e2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -46,6 +46,7 @@
 import com.android.permissioncontroller.DeviceUtils;
 import com.android.permissioncontroller.PermissionControllerStatsLog;
 import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.debug.PermissionDetailsFragment;
 import com.android.permissioncontroller.permission.debug.PermissionUsageFragment;
 import com.android.permissioncontroller.permission.debug.PermissionUsageV2Fragment;
 import com.android.permissioncontroller.permission.debug.UtilsKt;
@@ -163,6 +164,17 @@
                 }
             } break;
 
+            case Intent.ACTION_REVIEW_PERMISSION_HISTORY: {
+                if (UtilsKt.isPrivacyHubEnabled()) {
+                    String groupName = getIntent()
+                            .getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME);
+                    androidXFragment = PermissionDetailsFragment
+                            .newInstance(groupName, Long.MAX_VALUE);
+                }
+
+                break;
+            }
+
             case Intent.ACTION_MANAGE_APP_PERMISSION: {
                 if (DeviceUtils.isAuto(this) || DeviceUtils.isTelevision(this)
                         || DeviceUtils.isWear(this)) {
@@ -284,6 +296,8 @@
                 }
             } break;
 
+            case Intent.ACTION_MANAGE_UNUSED_APPS :
+                // fall through
             case ACTION_MANAGE_AUTO_REVOKE: {
                 Log.i(LOG_TAG, "sessionId " + sessionId + " starting auto revoke fragment"
                         + " from notification");
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionHistoryPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionHistoryPreference.java
new file mode 100644
index 0000000..45f3e3d
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionHistoryPreference.java
@@ -0,0 +1,59 @@
+/*
+ * 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.handheld;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.permissioncontroller.R;
+
+/**
+ * Preference for the permission history page
+ */
+public class PermissionHistoryPreference extends Preference {
+    private final @NonNull Context mContext;
+    private final String mAccessTime;
+    private final Drawable mPermissionIcon;
+
+    public PermissionHistoryPreference(@NonNull Context context, @NonNull String accessTime,
+            @NonNull Drawable permissionIcon) {
+        super(context);
+        mContext = context;
+        mAccessTime = accessTime;
+        mPermissionIcon = permissionIcon;
+
+        setWidgetLayoutResource(R.layout.permission_history_widget);
+    }
+
+    @Override
+    public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        TextView permissionHistoryTime = (TextView) holder
+                .findViewById(R.id.permission_history_time);
+        ImageView permissionIcon = (ImageView) holder.findViewById(R.id.permission_history_icon);
+
+        permissionHistoryTime.setText(mAccessTime);
+        permissionIcon.setImageDrawable(mPermissionIcon);
+    }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionUsageV2ControlPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionUsageV2ControlPreference.java
new file mode 100644
index 0000000..a122f08
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionUsageV2ControlPreference.java
@@ -0,0 +1,59 @@
+/*
+ * 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.handheld;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * Preference for the top level privacy hub page
+ */
+public class PermissionUsageV2ControlPreference extends Preference {
+    private final @NonNull Context mContext;
+    private @Nullable CharSequence mGroupName;
+
+    public PermissionUsageV2ControlPreference(@NonNull Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        setOnPreferenceClickListener((preference) -> {
+            Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY);
+            intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, mGroupName);
+
+            mContext.startActivity(intent);
+            return true;
+        });
+    }
+
+    public void setGroup(CharSequence groupName) {
+        this.mGroupName = groupName;
+    }
+
+    public CharSequence getGroupName() {
+        return this.mGroupName;
+    }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java
index 771950d..89938fa 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java
@@ -82,9 +82,11 @@
         return fragment;
     }
 
+    // create new ViewModel in onStart, because viewModel is sometimes persisting after finish()
+    // TODO: determine why viewModel is doing this.
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public void onStart() {
+        super.onStart();
 
         ReviewOngoingUsageViewModelFactory factory =
                 new ReviewOngoingUsageViewModelFactory(
@@ -113,9 +115,18 @@
             } else {
                 updateDialogView(usages);
             }
+            mViewModel.getUsages().removeObservers(this);
         });
     }
 
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (mDialog != null && getActivity() != null && !getActivity().isFinishing()) {
+            mDialog.dismiss();
+        }
+    }
+
     /**
      * Get a list of permission labels.
      *
diff --git a/PermissionController/src/com/android/permissioncontroller/role/Role.md b/PermissionController/src/com/android/permissioncontroller/role/Role.md
index ae14025..686c191 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/Role.md
+++ b/PermissionController/src/com/android/permissioncontroller/role/Role.md
@@ -46,16 +46,20 @@
 express certain behavior specific to the role.
 - `defaultHolders`: Optional name of a system config resource that designates the default holders of
 the role, e.g. `config_defaultSms`. If the role is not exclusive, multiple package names can be
-specified by separating them with semicolon (`;`).
+specified by separating them with a semicolon (`;`).
 - `description`: The string resource for the description of the role, e.g.
 `@string/role_sms_description`, which says "Apps that allow you to use your phone number to send and
 receive short text messages, photos, videos, and more". For default apps, this string will appear in
 the default app detail page as a footer. This attribute is required if the role is `visible`.
 - `exclusive`: Whether the role is exclusive. If a role is exclusive, at most one application is
 allowed to be its holder.
+- `fallBackToDefaultHolder`: Whether the role should fall back to the default holder. This attribute
+is optional and defaults to `false`.
 - `label`: The string resource for the label of the role, e.g. `@string/role_sms_label`, which says
 "Default SMS app". For default apps, this string will appear in the default app detail page as the
 title. This attribute is required if the role is `visible`.
+- `minSdkVersion`: The minimum SDK version for the role to be available, e.g. `31` for Android S.
+This attribute is optional and defaults to `Build.VERSION_CODES.BASE`.
 - `requestDescription`: The string resource for the description in the request role dialog, e.g.
 `@string/role_sms_request_description`, which says "Gets access to contacts, SMS, phone". This
 description should describe to the user the privileges that are going to be granted, and should not
diff --git a/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md b/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md
index c7f7f47..1c3486e 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md
+++ b/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md
@@ -56,6 +56,7 @@
     name="android.app.role.SYSTEM_YOUR_ROLE_NAME"
     defaultHolders="config_systemYourRoleName"
     exclusive="true"
+    minSdkVersion="31"
     systemOnly="true"
     visible="false">
     <permissions>
@@ -66,7 +67,9 @@
 
 The role is named with the `SYSTEM_` prefix to indicate that it is an invisible and system app only
 role. The config resource `config_systemYourRoleName` will be added in the next step, and more
-details about role are available in [Android Role for system developers](Role.md).
+details about role are available in [Android Role for system developers](Role.md). The
+`minSdkVersion` attribute should normally be set to the current SDK version, to avoid making the
+role available on older platforms.
 
 If you are writing a CTS test for an API protected by an `internal|role` permission, you probably
 want to grant the permission to Shell as well. This can be achieved by adding the permission to the
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/Role.java b/PermissionController/src/com/android/permissioncontroller/role/model/Role.java
index 8bf2781..bd411c3 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/Role.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/model/Role.java
@@ -24,6 +24,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -112,6 +113,11 @@
     private final int mLabelResource;
 
     /**
+     * The minimum SDK version for this role to be available.
+     */
+    private final int mMinSdkVersion;
+
+    /**
      * Whether this role should override user's choice about privileges when granting.
      */
     private final boolean mOverrideUserWhenGranting;
@@ -199,13 +205,13 @@
     public Role(@NonNull String name, @Nullable RoleBehavior behavior,
             @Nullable String defaultHoldersResourceName, @StringRes int descriptionResource,
             boolean exclusive, boolean fallBackToDefaultHolder, @StringRes int labelResource,
-            boolean overrideUserWhenGranting, @StringRes int requestDescriptionResource,
-            @StringRes int requestTitleResource, boolean requestable,
-            @StringRes int searchKeywordsResource, @StringRes int shortLabelResource,
-            boolean showNone, boolean systemOnly, boolean visible,
-            @NonNull List<RequiredComponent> requiredComponents, @NonNull List<String> permissions,
-            @NonNull List<String> appOpPermissions, @NonNull List<AppOp> appOps,
-            @NonNull List<PreferredActivity> preferredActivities) {
+            int minSdkVersion, boolean overrideUserWhenGranting,
+            @StringRes int requestDescriptionResource, @StringRes int requestTitleResource,
+            boolean requestable, @StringRes int searchKeywordsResource,
+            @StringRes int shortLabelResource, boolean showNone, boolean systemOnly,
+            boolean visible, @NonNull List<RequiredComponent> requiredComponents,
+            @NonNull List<String> permissions, @NonNull List<String> appOpPermissions,
+            @NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities) {
         mName = name;
         mBehavior = behavior;
         mDefaultHoldersResourceName = defaultHoldersResourceName;
@@ -213,6 +219,7 @@
         mExclusive = exclusive;
         mFallBackToDefaultHolder = fallBackToDefaultHolder;
         mLabelResource = labelResource;
+        mMinSdkVersion = minSdkVersion;
         mOverrideUserWhenGranting = overrideUserWhenGranting;
         mRequestDescriptionResource = requestDescriptionResource;
         mRequestTitleResource = requestTitleResource;
@@ -340,6 +347,16 @@
      * @return whether this role is available.
      */
     public boolean isAvailableAsUser(@NonNull UserHandle user, @NonNull Context context) {
+        // Workaround to match the value 31 for S in roles.xml before SDK finalization.
+        if (mMinSdkVersion == 31) {
+            if (!SdkLevel.isAtLeastS()) {
+                return false;
+            }
+        } else {
+            if (Build.VERSION.SDK_INT < mMinSdkVersion) {
+                return false;
+            }
+        }
         if (mBehavior != null) {
             return mBehavior.isAvailableAsUser(this, user, context);
         }
@@ -892,6 +909,7 @@
                 + ", mExclusive=" + mExclusive
                 + ", mFallBackToDefaultHolder=" + mFallBackToDefaultHolder
                 + ", mLabelResource=" + mLabelResource
+                + ", mMinSdkVersion=" + mMinSdkVersion
                 + ", mOverrideUserWhenGranting=" + mOverrideUserWhenGranting
                 + ", mRequestDescriptionResource=" + mRequestDescriptionResource
                 + ", mRequestTitleResource=" + mRequestTitleResource
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RoleParser.java b/PermissionController/src/com/android/permissioncontroller/role/model/RoleParser.java
index c432a8e..0954be7 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RoleParser.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/model/RoleParser.java
@@ -77,6 +77,7 @@
     private static final String ATTRIBUTE_EXCLUSIVE = "exclusive";
     private static final String ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER = "fallBackToDefaultHolder";
     private static final String ATTRIBUTE_LABEL = "label";
+    private static final String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion";
     private static final String ATTRIBUTE_OVERRIDE_USER_WHEN_GRANTING = "overrideUserWhenGranting";
     private static final String ATTRIBUTE_REQUEST_TITLE = "requestTitle";
     private static final String ATTRIBUTE_REQUEST_DESCRIPTION = "requestDescription";
@@ -89,8 +90,6 @@
     private static final String ATTRIBUTE_PERMISSION = "permission";
     private static final String ATTRIBUTE_SCHEME = "scheme";
     private static final String ATTRIBUTE_MIME_TYPE = "mimeType";
-    private static final String ATTRIBUTE_VALUE = "value";
-    private static final String ATTRIBUTE_OPTIONAL = "optional";
     private static final String ATTRIBUTE_MAX_TARGET_SDK_VERSION = "maxTargetSdkVersion";
     private static final String ATTRIBUTE_MODE = "mode";
 
@@ -326,6 +325,9 @@
         boolean fallBackToDefaultHolder = getAttributeBooleanValue(parser,
                 ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER, false);
 
+        int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION,
+                Build.VERSION_CODES.BASE);
+
         boolean overrideUserWhenGranting = getAttributeBooleanValue(parser,
                 ATTRIBUTE_OVERRIDE_USER_WHEN_GRANTING, true);
 
@@ -442,7 +444,7 @@
             preferredActivities = Collections.emptyList();
         }
         return new Role(name, behavior, defaultHoldersResourceName, descriptionResource, exclusive,
-                fallBackToDefaultHolder, labelResource, overrideUserWhenGranting,
+                fallBackToDefaultHolder, labelResource, minSdkVersion, overrideUserWhenGranting,
                 requestDescriptionResource, requestTitleResource, requestable,
                 searchKeywordsResource, shortLabelResource, showNone, systemOnly, visible,
                 requiredComponents, permissions, appOpPermissions, appOps,
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/SystemTelevisionNotificationHandler.java b/PermissionController/src/com/android/permissioncontroller/role/model/SystemTelevisionNotificationHandler.java
new file mode 100644
index 0000000..cbf1565
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/model/SystemTelevisionNotificationHandler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.role.model;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Class for behavior of the Television Notification Handler role.
+ */
+public class SystemTelevisionNotificationHandler implements RoleBehavior {
+
+    @Override
+    public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user,
+            @NonNull Context context) {
+        // Role is only available on Leanback devices
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+}