[automerger skipped] Merge mainline-release 6664920 to stage-aosp-master - DO NOT MERGE am: 6ec6bf7c8b -s ours am: 0baa475205 -s ours am: b199a570d4 -s ours

am skip reason: Change-Id I929cfc3549e4b5790a15b08f5a618d03f9987ae3 with SHA-1 3c04de1557 is in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/PackageInstaller/+/12579360

Change-Id: I75f435f0e0405038f231e40b1fe66f6e0e4de69f
diff --git a/Android.bp b/Android.bp
index 7b50233..8d3b2de 100644
--- a/Android.bp
+++ b/Android.bp
@@ -94,7 +94,7 @@
         "SettingsLibProgressBar",
         "androidx.annotation_annotation",
         "permissioncontroller-statsd",
-        "car-ui-lib",
+        "car-ui-lib-overlayable",
         "libprotobuf-java-lite",
         "SettingsLibUtils",
     ],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f8856ae..08d4d0b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -45,6 +45,8 @@
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
 
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
@@ -122,6 +124,7 @@
                 <action android:name="android.intent.action.MANAGE_APP_PERMISSION" />
                 <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" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -132,6 +135,7 @@
                   android:theme="@android:style/Theme.NoDisplay">
             <intent-filter android:priority="1">
                 <action android:name="com.android.permissioncontroller.settingssearch.action.MANAGE_PERMISSION_APPS" />
+                <action android:name="com.android.permissioncontroller.settingssearch.action.REVIEW_PERMISSION_USAGE" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -160,6 +164,17 @@
                   android:excludeFromRecents="true"
                   android:theme="@style/PermissionDialog.FilterTouches" />
 
+        <activity android:name="com.android.permissioncontroller.permission.ui.ReviewOngoingUsageActivity"
+                  android:excludeFromRecents="true"
+                  android:theme="@style/PermissionDialog"
+                  android:launchMode="singleInstance"
+                  android:permission="android.permission.GRANT_RUNTIME_PERMISSIONS" >
+            <intent-filter android:priority="1">
+                <action android:name="android.intent.action.REVIEW_ONGOING_PERMISSION_USAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="com.android.permissioncontroller.permission.ui.ReviewAccessibilityServicesActivity"
                   android:excludeFromRecents="true"
                   android:theme="@style/PermissionDialog.FilterTouches"
diff --git a/res/layout/image_view.xml b/res/layout/image_view.xml
index f2906b2..22d9726 100644
--- a/res/layout/image_view.xml
+++ b/res/layout/image_view.xml
@@ -19,7 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     style="@style/ImageView">
 
-        <com.android.permissioncontroller.permission.ui.PreferenceImageView
+        <com.android.permissioncontroller.permission.debug.PreferenceImageView
             android:id="@+id/icon"
             style="@style/ImageViewIcon" />
 
diff --git a/res/layout/ongoing_usage_dialog_content.xml b/res/layout/ongoing_usage_dialog_content.xml
new file mode 100644
index 0000000..f9a0f07
--- /dev/null
+++ b/res/layout/ongoing_usage_dialog_content.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/container"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            style="@style/PermissionUsageDialogContainerScrollView">
+
+    <LinearLayout
+        android:id="@+id/dialog_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        style="@style/PermissionUsageDialogContainerLayout">
+
+        <TextView
+            android:id="@+id/title"
+            style="@style/PermissionUsageDialogTitle"/>
+
+        <LinearLayout
+            android:id="@+id/items_container"
+            style="@style/PermissionUsageDialogItemsContainer"/>
+
+        <TextView
+            android:id="@+id/other_use_header"
+            android:text="@string/other_use"
+            style="@style/PermissionUsageDialogOtherUseHeader"/>
+
+        <TextView
+            android:id="@+id/other_use_content"
+            style="@style/PermissionUsageDialogOtherUseContent"/>
+
+        <View
+            android:id="@+id/other_use_inside_spacer"
+            style="@style/PermissionUsageDialogOtherUseInsideSpacer"/>
+
+        <TextView
+            android:id="@+id/system_use_content"
+            style="@style/PermissionUsageDialogSystemUseContent"/>
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/res/layout/ongoing_usage_dialog_item.xml b/res/layout/ongoing_usage_dialog_item.xml
new file mode 100644
index 0000000..b18f152
--- /dev/null
+++ b/res/layout/ongoing_usage_dialog_item.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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"
+              style="@style/PermissionUsageDialogItemContainer">
+
+    <FrameLayout
+        style="@style/PermissionUsageDialogItemIconFrame">
+
+        <ImageView
+            android:id="@+id/app_icon"
+            style="@style/PermissionUsageDialogItemIcon"/>
+
+    </FrameLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                  style="@style/PermissionUsageDialogAppAndPermissions">
+
+        <TextView
+            android:id="@+id/app_name"
+            style="@style/PermissionUsageDialogItemAppName"/>
+
+        <TextView
+            android:id="@+id/permissionsList"
+            style="@style/PermissionUsageDialogItemPermissionsList"
+            android:visibility="gone" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/icons"
+        style="@style/PermissionUsageDialogItemIconsContainer"
+        android:visibility="gone" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/title_summary_image_view.xml b/res/layout/title_summary_image_view.xml
index 31eaa1f..75b51e1 100644
--- a/res/layout/title_summary_image_view.xml
+++ b/res/layout/title_summary_image_view.xml
@@ -20,7 +20,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
 
-        <com.android.permissioncontroller.permission.ui.PreferenceImageView
+        <com.android.permissioncontroller.permission.debug.PreferenceImageView
             android:id="@+id/icon"
             style="@style/TitleSummaryImageViewIcon" />
 
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index b90b743..aa683a4 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -149,8 +149,14 @@
             <item type="style" name="PermissionUsageDialogItemContainer" />
             <item type="style" name="PermissionUsageDialogItemIconFrame" />
             <item type="style" name="PermissionUsageDialogItemIcon" />
+            <item type="style" name="PermissionUsageDialogAppAndPermissions" />
             <item type="style" name="PermissionUsageDialogItemAppName" />
+            <item type="style" name="PermissionUsageDialogItemPermissionsList" />
             <item type="style" name="PermissionUsageDialogItemIconsContainer" />
+            <item type="style" name="PermissionUsageDialogOtherUseHeader" />
+            <item type="style" name="PermissionUsageDialogOtherUseContent" />
+            <item type="style" name="PermissionUsageDialogOtherUseInsideSpacer" />
+            <item type="style" name="PermissionUsageDialogSystemUseContent" />
             <!-- END ONGOING USAGE DIALOG -->
 
             <!-- START REQUEST ROLE DIALOG TITLE -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9180ca9..e7804ff 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -287,9 +287,114 @@
     <!-- Help URL, application permissions [DO NOT TRANSLATE] -->
     <string name="help_app_permissions" translatable="false"></string>
 
+    <!-- Title for permission usage [CHAR LIMIT=30] -->
+    <string name="permission_usage_title">Dashboard</string>
+
+    <!-- Summary for showing a single permission access and the number of accesses [CHAR LIMIT=80] -->
+    <plurals name="permission_usage_summary">
+        <item quantity="one">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\n<xliff:g id="num" example="42">%2$s</xliff:g> access</item>
+        <item quantity="other">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\n<xliff:g id="num" example="42">%2$s</xliff:g> accesses</item>
+    </plurals>
+
+    <!-- Summary for showing a single permission access and the number of accesses, including those in the background [CHAR LIMIT=80] -->
+    <plurals name="permission_usage_summary_background">
+        <item quantity="one">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\n<xliff:g id="num" example="42">%2$s</xliff:g> access (<xliff:g id="num" example="7">%3$s</xliff:g> in background)</item>
+        <item quantity="other">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\n<xliff:g id="num" example="42">%2$s</xliff:g> accesses (<xliff:g id="num" example="7">%3$s</xliff:g> in background)</item>
+    </plurals>
+
+    <!-- Summary for showing a single permission access and the number of accesses [CHAR LIMIT=120] -->
+    <plurals name="permission_usage_summary_duration">
+        <item quantity="one">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\n<xliff:g id="num" example="42">%2$s</xliff:g> access\nDuration: <xliff:g id="time" example="2 hours">%3$s</xliff:g></item>
+        <item quantity="other">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\n<xliff:g id="num" example="42">%2$s</xliff:g> accesses\nDuration: <xliff:g id="time" example="2 hours">%3$s</xliff:g></item>
+    </plurals>
+
+    <!-- Summary for showing a single permission access and the number of accesses, including those in the background [CHAR LIMIT=120] -->
+    <plurals name="permission_usage_summary_background_duration">
+        <item quantity="one">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\n<xliff:g id="num" example="42">%2$s</xliff:g> access (<xliff:g id="num" example="7">%3$s</xliff:g> in background)\nDuration: <xliff:g id="time" example="2 hours">%3$s</xliff:g></item>
+        <item quantity="other">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\n<xliff:g id="num" example="42">%2$s</xliff:g> accesses (<xliff:g id="num" example="7">%3$s</xliff:g> in background)\nDuration: <xliff:g id="time" example="2 hours">%3$s</xliff:g></item>
+    </plurals>
+
+    <!-- Summary for showing a single permission access and the time of the last access when it was in the background [CHAR LIMIT=80] -->
+    <string name="permission_usage_summary_background">Last access: <xliff:g id="time" example="12:10 PM">%1$s</xliff:g>\nLast accessed in the background</string>
+
+    <!-- Description for showing permission accesses with any permission [CHAR LIMIT=30] -->
+    <string name="permission_usage_any_permission">Any permission</string>
+
+    <!-- Description for showing permission accesses accessed any time [CHAR LIMIT=30] -->
+    <string name="permission_usage_any_time">Any time</string>
+
+    <!-- Description for showing permissions accessed in the last 7 days [CHAR LIMIT=30] -->
+    <string name="permission_usage_last_7_days">Last 7 days</string>
+
+    <!-- Description for showing permissions accessed in the last day [CHAR LIMIT=30] -->
+    <string name="permission_usage_last_day">Last 24 hours</string>
+
+    <!-- Description for showing permissions accessed in the last hour [CHAR LIMIT=30] -->
+    <string name="permission_usage_last_hour">Last 1 hour</string>
+
+    <!-- Description for showing permissions accessed in the last 15 minutes [CHAR LIMIT=30] -->
+    <string name="permission_usage_last_15_minutes">Last 15 minutes</string>
+
+    <!-- Description for showing permissions accessed in the last minute [CHAR LIMIT=30] -->
+    <string name="permission_usage_last_minute">Last 1 minute</string>
+
+    <!-- Label when no apps have used the requested permissions [CHAR LIMIT=30] -->
+    <string name="no_permission_usages">No permission usages</string>
+
+    <!-- Label for the title of the list of permission usages that shows which apps used which permissions[CHAR LIMIT=50] -->
+    <string name="permission_usage_list_title_any_time">Most recent access at any time</string>
+
+    <!-- Label for the title of the list of permission usages that shows which apps used which permissions[CHAR LIMIT=50] -->
+    <string name="permission_usage_list_title_last_7_days">Most recent access in last 7 days</string>
+
+    <!-- Label for the title of the list of permission usages that shows which apps used which permissions[CHAR LIMIT=50] -->
+    <string name="permission_usage_list_title_last_day">Most recent access in last 24 hours</string>
+
+    <!-- Label for the title of the list of permission usages that shows which apps used which permissions[CHAR LIMIT=50] -->
+    <string name="permission_usage_list_title_last_hour">Most recent access in last 1 hour</string>
+
+    <!-- Label for the title of the list of permission usages that shows which apps used which permissions[CHAR LIMIT=50] -->
+    <string name="permission_usage_list_title_last_15_minutes">Most recent access in last 15 minutes</string>
+
+    <!-- Label for the title of the list of permission usages that shows which apps used which permissions[CHAR LIMIT=50] -->
+    <string name="permission_usage_list_title_last_minute">Most recent access in last 1 minute</string>
+
+    <!-- Label for the title of the permission bar chart showing how often the most common permissions are used [CHAR LIMIT=50] -->
+    <string name="permission_usage_bar_chart_title_any_time">Permission usage at any time</string>
+
+    <!-- Label for the title of the permission bar chart showing how often the most common permissions are used [CHAR LIMIT=50] -->
+    <string name="permission_usage_bar_chart_title_last_7_days">Permission usage in last 7 days</string>
+
+    <!-- Label for the title of the permission bar chart showing how often the most common permissions are used [CHAR LIMIT=50] -->
+    <string name="permission_usage_bar_chart_title_last_day">Permission usage in last 24 hours</string>
+
+    <!-- Label for the title of the permission bar chart showing how often the most common permissions are used [CHAR LIMIT=50] -->
+    <string name="permission_usage_bar_chart_title_last_hour">Permission usage in last 1 hour</string>
+
+    <!-- Label for the title of the permission bar chart showing how often the most common permissions are used [CHAR LIMIT=50] -->
+    <string name="permission_usage_bar_chart_title_last_15_minutes">Permission usage in last 15 minutes</string>
+
+    <!-- Label for the title of the permission bar chart showing how often the most common permissions are used [CHAR LIMIT=50] -->
+    <string name="permission_usage_bar_chart_title_last_minute">Permission usage in last 1 minute</string>
+
+    <!-- Label for the bars on the chart that shows how many apps have used various permissions [CHAR LIMIT=10] -->
+    <plurals name="permission_usage_bar_label">
+        <item quantity="one">1 app</item>
+        <item quantity="other"><xliff:g id="number" example="7">%s</xliff:g> apps</item>
+    </plurals>
+
+    <!-- Label for the button to bring the user to view the details of recent permission accesses [CHAR LIMIT=42] -->
+    <string name="permission_usage_view_details">See all in Dashboard</string>
+
     <!-- DO NOT TRANSLATE Summary placeholder -->
     <string name="summary_placeholder" translatable="false">&#160;</string>
 
+    <!-- Label for filtered view that shows permission usages of a single permission [CHAR LIMIT=40] -->
+    <string name="app_permission_usage_filter_label">Filtered by: <xliff:g id="perm" example="Location">%1$s</xliff:g> </string>
+
+    <!-- Label for the text that removes the filter by permission to view all usages [CHAR LIMIT=none] -->
+    <string name="app_permission_usage_remove_filter">Remove filter</string>
+
     <!-- Label for the title of the dialog allowing filtering by permissions [CHAR LIMIT=none] -->
     <string name="filter_by_title">Filter by</string>
 
@@ -299,9 +404,45 @@
     <!-- Label for the menu item allowing filtering by time [CHAR LIMIT=none] -->
     <string name="filter_by_time">Filter by time</string>
 
+    <!-- Label for sorting usages by the number of permissions used [CHAR LIMIT=30] -->
+    <string name="sort_spinner_most_permissions">Most permissions</string>
+
+    <!-- Label for sorting usages by the number of accesses [CHAR LIMIT=30] -->
+    <string name="sort_spinner_most_accesses">Most accesses</string>
+
+    <!-- Label for sorting usages by the most recent accesses [CHAR LIMIT=30] -->
+    <string name="sort_spinner_recent">Recent</string>
+
+    <!-- Label for sorting usages by which app used a permission most recently [CHAR LIMIT=30] -->
+    <string name="sort_by_app">Sort by app usage</string>
+
+    <!-- Label for sorting usages by access time [CHAR LIMIT=30] -->
+    <string name="sort_by_time">Sort by time</string>
+
     <!-- Separator for a list of items. Include spaces before and after if needed [CHAR LIMIT=10] -->
     <string name="item_separator">,\u0020</string>
 
+    <!-- Label for refreshing the list of permission usages. [CHAR LIMIT=30] -->
+    <string name="permission_usage_refresh">Refresh</string>
+
+    <!-- Subtitle for showing how many apps have accessed a given permission [CHAR LIMIT=20] -->
+    <plurals name="permission_usage_permission_filter_subtitle">
+        <item quantity="one">1 app</item>
+        <item quantity="other"><xliff:g id="number" example="7">%s</xliff:g> apps</item>
+    </plurals>
+
+    <!-- Help URL, permission usage [DO NOT TRANSLATE] -->
+    <string name="help_permission_usage" translatable="false"></string>
+
+    <!-- Title for permission usage [CHAR LIMIT=30] -->
+    <string name="app_permission_usage_title">App permissions usage</string>
+
+    <!-- Summary for an app's use of a permission [CHAR LIMIT=none] -->
+    <string name="app_permission_usage_summary">Access: <xliff:g id="num" example="2">%1$s</xliff:g> times. Total duration: <xliff:g id="duration" example="2 hours">%2$s</xliff:g>. Last used <xliff:g id="time" example="2 hours">%3$s</xliff:g> ago.</string>
+
+    <!-- Summary for an app's use of a permission without duration [CHAR LIMIT=none] -->
+    <string name="app_permission_usage_summary_no_duration">Access: <xliff:g id="num" example="2">%1$s</xliff:g> times. Last used <xliff:g id="time" example="2 hours">%2$s</xliff:g> ago.</string>
+
     <!-- Title for the dialog button to allow a permission grant when you cannot only allow in the foreground. [CHAR LIMIT=60] -->
     <string name="app_permission_button_allow">Allow</string>
 
@@ -335,6 +476,9 @@
     <!-- Text for linking to the page that shows the apps with a given permission [CHAR LIMIT=none] -->
     <string name="app_permission_footer_permission_apps_link">See all apps with this permission</string>
 
+    <!-- Label for the assistant mic display switch [CHAR LIMIT=60] -->
+    <string name="assistant_mic_label">Show assistant microphone usage</string>
+
     <!-- Label for the auto revoke switch [CHAR LIMIT=60] -->
     <string name="auto_revoke_label">Remove permissions if app isn\u2019t used</string>
 
@@ -740,9 +884,41 @@
     <!-- Label for the button to set an application as the default application [CHAR LIMIT=20] -->
     <string name="request_role_set_as_default">Set as default</string>
 
+    <!-- Message telling the user that a phone call is currently using the microphone [CHAR LIMIT=none] -->
+    <string name="phone_call_uses_microphone">Microphone is used in &lt;b>phone call&lt;/b></string>
+    <!-- Message telling the user that a phone call is currently using the microphone and the camera [CHAR LIMIT=none] -->
+    <string name="phone_call_uses_microphone_and_camera">Camera and Microphone are used in &lt;b>video call&lt;/b></string>
+    <!-- Message telling the user that a phone call is currently using the camera [CHAR LIMIT=none] -->
+    <string name="phone_call_uses_camera">Camera is used in &lt;b>video call&lt;/b></string>
+
+    <!-- Message telling the user that a system service is currently using the microphone [CHAR LIMIT=none] -->
+    <string name="system_uses_microphone">Microphone is accessed using system service</string>
+    <!-- Message telling the user that a system service is currently using the microphone and the camera [CHAR LIMIT=none] -->
+    <string name="system_uses_microphone_and_camera">Camera and Microphone are accessed using system service</string>
+    <!-- Message telling the user that a system service is currently using the camera [CHAR LIMIT=none] -->
+    <string name="system_uses_camera">Camera is accessed using system service</string>
+
+    <!-- Line above a list of other apps and system service that are currently microphone or camera [CHAR LIMIT=60] -->
+    <string name="other_use">Other use:</string>
+
+    <!-- Action for accepting the Ongoing usage dialog [CHAR LIMIT=10]-->
+    <string name="ongoing_usage_dialog_ok">Got it</string>
+
+    <!-- Title for Ongoing usage dialog title when multiple apps are using app ops [CHAR LIMIT=NONE] -->
+    <string name="ongoing_usage_dialog_title">Recent use of <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string>
+
+    <!-- Separator for permissions. Include spaces before and after if needed [CHAR LIMIT=10] -->
+    <string name="ongoing_usage_dialog_separator">,\u0020</string>
+
+    <!-- Separator for permissions, before last type. Include spaces before and after if needed [CHAR LIMIT=10] -->
+    <string name="ongoing_usage_dialog_last_separator">\u0020and\u0020</string>
+
     <!-- Keyword in the Settings app's search functionality that can be used to find links to the default app management screens [CHAR LIMIT=none] -->
     <string name="default_app_search_keyword">default apps</string>
 
+    <!-- List of the two permission group names for microphone (android:string/permgrouplab_microphone) and camera (android:string/permgrouplab_camera) [CHAR LIMIT=60]-->
+    <string name="permgroup_list_microphone_and_camera">Microphone &amp; Camera</string>
+
     <!-- Accessibility label for button that opens a settings screen [CHAR LIMIT=NONE] -->
     <string name="settings_button">Settings</string>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 188c29a..c1c8e2d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -630,24 +630,63 @@
         <item name="android:layout_gravity">center</item>
     </style>
 
-    <style name="PermissionUsageDialogItemAppName"
-           parent="@android:style/TextAppearance.DeviceDefault">
+    <style name="PermissionUsageDialogAppAndPermissions">
         <item name="android:layout_width">0dp</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:layout_weight">1</item>
+        <item name="android:orientation">vertical</item>
         <item name="android:gravity">start|center_vertical</item>
+    </style>
+
+    <style name="PermissionUsageDialogItemAppName"
+           parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
         <item name="android:textDirection">locale</item>
         <item name="android:textSize">16sp</item>
         <item name="android:layout_marginStart">16dp</item>
     </style>
 
+    <style name="PermissionUsageDialogItemPermissionsList">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textAppearance">?android:textAppearanceListItemSecondary</item>
+        <item name="android:textColor">?android:textColorSecondary</item>
+        <item name="android:layout_marginStart">16dp</item>
+    </style>
+
     <style name="PermissionUsageDialogItemIconsContainer">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">match_parent</item>
         <item name="android:orientation">horizontal</item>
         <item name="android:gravity">end|center_vertical</item>
         <item name="android:layout_gravity">end</item>
-        <item name="android:visibility">gone</item>
+    </style>
+
+    <style name="PermissionUsageDialogOtherUseHeader">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textAppearance">?android:textAppearanceListItemSecondary</item>
+        <item name="android:layout_marginStart">16dp</item>
+    </style>
+
+    <style name="PermissionUsageDialogOtherUseContent">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textAppearance">?android:textAppearanceListItemSecondary</item>
+        <item name="android:layout_marginStart">16dp</item>
+    </style>
+
+    <style name="PermissionUsageDialogOtherUseInsideSpacer">
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">16dp</item>
+    </style>
+
+    <style name="PermissionUsageDialogSystemUseContent">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textAppearance">?android:textAppearanceListItemSecondary</item>
+        <item name="android:layout_marginStart">16dp</item>
     </style>
 
     <!-- END ONGOING USAGE DIALOG -->
diff --git a/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt b/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
index 7f0a104..71cb3ef 100644
--- a/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
+++ b/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
@@ -54,6 +54,7 @@
 ) : SmartUpdateMediatorLiveData<AppPermGroupUiInfo>(), LocationUtils.LocationListener {
 
     private var isSpecialLocation = false
+    private val isMicrophone = permGroupName == Manifest.permission_group.MICROPHONE
     private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user]
     private val permGroupLiveData = PermGroupLiveData[permGroupName]
     private val permissionStateLiveData = PermStateLiveData[packageName, permGroupName, user]
@@ -294,7 +295,6 @@
 
     override fun onActive() {
         super.onActive()
-
         if (isSpecialLocation) {
             LocationUtils.addLocationListener(this)
             updateIfActive()
diff --git a/src/com/android/permissioncontroller/permission/data/OpUsageLiveData.kt b/src/com/android/permissioncontroller/permission/data/OpUsageLiveData.kt
new file mode 100644
index 0000000..3d03bde
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/data/OpUsageLiveData.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 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.data
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED
+import android.app.Application
+import android.os.UserHandle
+import android.util.Log
+import com.android.permissioncontroller.PermissionControllerApplication
+import kotlinx.coroutines.Job
+
+/**
+ * LiveData that loads the last usage of each of a list of app ops for every package.
+ *
+ * <p>For app-ops with duration the end of the access is considered.
+ *
+ * @param app The current application
+ * @param opNames The names of the app ops we wish to search for
+ * @param usageDurationMs how much ago can an access have happened to be considered
+ */
+// TODO: listen for updates
+class OpUsageLiveData(
+    private val app: Application,
+    private val opNames: List<String>,
+    private val usageDurationMs: Long
+) : SmartAsyncMediatorLiveData<@JvmSuppressWildcards Map<String, List<OpAccess>>>() {
+    val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
+
+    override suspend fun loadDataAndPostValue(job: Job) {
+        val now = System.currentTimeMillis()
+        val opMap = mutableMapOf<String, MutableList<OpAccess>>()
+
+        val packageOps = appOpsManager.getPackagesForOps(opNames.toTypedArray())
+        for (packageOp in packageOps) {
+            for (opEntry in packageOp.ops) {
+                val user = UserHandle.getUserHandleForUid(packageOp.uid)
+                val lastAccessTime: Long = opEntry.getLastAccessTime(OP_FLAGS_ALL_TRUSTED)
+
+                if (lastAccessTime == -1L) {
+                    // There was no access, so skip
+                    continue
+                }
+
+                var lastAccessDuration = opEntry.getLastDuration(OP_FLAGS_ALL_TRUSTED)
+
+                // Some accesses have no duration
+                if (lastAccessDuration == -1L) {
+                    lastAccessDuration = 0
+                }
+
+                if (opEntry.isRunning ||
+                        lastAccessTime + lastAccessDuration > (now - usageDurationMs)) {
+                    val accessList = opMap.getOrPut(opEntry.opStr) { mutableListOf() }
+                    val accessTime = if (opEntry.isRunning) {
+                        -1
+                    } else {
+                        lastAccessTime
+                    }
+                    accessList.add(OpAccess(packageOp.packageName, user, accessTime))
+                    // TODO ntmyren: remove logs once b/160724034 is fixed
+                    Log.i("OpUsageLiveData", "adding ${opEntry.opStr} for " +
+                        "${packageOp.packageName}, access time of $lastAccessTime, isRunning: " +
+                        "${opEntry.isRunning} current time $now, duration $lastAccessDuration")
+                } else {
+                    // TODO ntmyren: remove logs once b/160724034 is fixed
+                    Log.i("OpUsageLiveData", "NOT adding ${opEntry.opStr} for " +
+                        "${packageOp.packageName}, access time of $lastAccessTime, isRunning: " +
+                        "${opEntry.isRunning} current time $now, duration $lastAccessDuration")
+                }
+            }
+        }
+
+        postValue(opMap)
+    }
+
+    override fun onActive() {
+        super.onActive()
+        updateAsync()
+    }
+
+    companion object : DataRepository<Pair<List<String>, Long>, OpUsageLiveData>() {
+        override fun newValue(key: Pair<List<String>, Long>): OpUsageLiveData {
+            return OpUsageLiveData(PermissionControllerApplication.get(), key.first, key.second)
+        }
+
+        operator fun get(ops: List<String>, usageDurationMs: Long): OpUsageLiveData {
+            return get(ops to usageDurationMs)
+        }
+    }
+}
+
+data class OpAccess(val packageName: String?, val user: UserHandle?, val lastAccessTime: Long) {
+    companion object {
+        const val IS_RUNNING = -1L
+    }
+
+    fun isRunning() = lastAccessTime == IS_RUNNING
+}
diff --git a/src/com/android/permissioncontroller/permission/debug/ExpandablePreferenceGroup.java b/src/com/android/permissioncontroller/permission/debug/ExpandablePreferenceGroup.java
new file mode 100644
index 0000000..676a63c
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/ExpandablePreferenceGroup.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 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 android.content.Context;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.permissioncontroller.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A preference group that expands/collapses its children when clicked.
+ */
+public class ExpandablePreferenceGroup extends PreferenceGroup {
+    private @NonNull Context mContext;
+    private @NonNull List<Preference> mPreferences;
+    private @NonNull List<Pair<Integer, CharSequence>> mSummaryIcons;
+    private boolean mExpanded;
+
+    public ExpandablePreferenceGroup(@NonNull Context context) {
+        super(context, null);
+
+        mContext = context;
+        mPreferences = new ArrayList<>();
+        mSummaryIcons = new ArrayList<>();
+        mExpanded = false;
+
+        setLayoutResource(R.layout.preference_usage);
+        setWidgetLayoutResource(R.layout.image_view);
+        setOnPreferenceClickListener(preference -> {
+            if (!mExpanded) {
+                int numPreferences = mPreferences.size();
+                for (int i = 0; i < numPreferences; i++) {
+                    super.addPreference(mPreferences.get(i));
+                }
+            } else {
+                removeAll();
+            }
+            mExpanded = !mExpanded;
+            return true;
+        });
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        ImageView icon = (ImageView) holder.findViewById(android.R.id.icon);
+        int rightIconSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.secondary_app_icon_size);
+        icon.setMaxWidth(rightIconSize);
+        icon.setMaxHeight(rightIconSize);
+
+        super.onBindViewHolder(holder);
+
+        TextView summary = (TextView) holder.findViewById(android.R.id.summary);
+        summary.setMaxLines(1);
+        summary.setEllipsize(TextUtils.TruncateAt.END);
+
+        ImageView rightImageView = holder.findViewById(
+                android.R.id.widget_frame).findViewById(R.id.icon);
+        if (mExpanded) {
+            rightImageView.setImageResource(R.drawable.ic_arrow_up);
+        } else {
+            rightImageView.setImageResource(R.drawable.ic_arrow_down);
+        }
+
+        holder.setDividerAllowedAbove(false);
+        holder.setDividerAllowedBelow(false);
+
+        holder.findViewById(R.id.title_widget_frame).setVisibility(View.GONE);
+
+        ViewGroup summaryFrame = (ViewGroup) holder.findViewById(R.id.summary_widget_frame);
+        if (mSummaryIcons.isEmpty()) {
+            summaryFrame.setVisibility(View.GONE);
+        } else {
+            summaryFrame.removeAllViews();
+            int numIcons = mSummaryIcons.size();
+            for (int i = 0; i < numIcons; i++) {
+                LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
+                ViewGroup group = (ViewGroup) inflater.inflate(R.layout.title_summary_image_view,
+                        null);
+                ImageView imageView = group.requireViewById(R.id.icon);
+                Pair<Integer, CharSequence> summaryIcons = mSummaryIcons.get(i);
+                imageView.setImageResource(summaryIcons.first);
+                if (summaryIcons.second != null) {
+                    imageView.setContentDescription(summaryIcons.second);
+                }
+                summaryFrame.addView(group);
+            }
+        }
+    }
+
+    @Override
+    public boolean addPreference(Preference preference) {
+        mPreferences.add(preference);
+        return true;
+    }
+
+    /**
+     * Show the given icon next to this preference's summary.
+     *
+     * @param resId the resourceId of the drawable to use as the icon.
+     */
+    public void addSummaryIcon(@DrawableRes int resId, @Nullable CharSequence contentDescription) {
+        mSummaryIcons.add(Pair.create(resId, contentDescription));
+    }
+}
diff --git a/src/com/android/permissioncontroller/permission/debug/PermissionUsageFragment.java b/src/com/android/permissioncontroller/permission/debug/PermissionUsageFragment.java
new file mode 100644
index 0000000..0888b63
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/PermissionUsageFragment.java
@@ -0,0 +1,1087 @@
+/*
+ * Copyright (C) 2020 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 android.Manifest.permission_group.CAMERA;
+import static android.Manifest.permission_group.LOCATION;
+import static android.Manifest.permission_group.MICROPHONE;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.Html;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.model.AppPermissionUsage;
+import com.android.permissioncontroller.permission.model.AppPermissionUsage.GroupUsage;
+import com.android.permissioncontroller.permission.model.AppPermissionGroup;
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
+import com.android.permissioncontroller.permission.ui.handheld.PermissionControlPreference;
+import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
+import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.settingslib.HelpUtils;
+import com.android.settingslib.widget.ActionBarShadowController;
+import com.android.settingslib.widget.BarChartInfo;
+import com.android.settingslib.widget.BarChartPreference;
+import com.android.settingslib.widget.BarViewInfo;
+
+import java.lang.annotation.Retention;
+import java.text.Collator;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Show the usage of all apps of all permission groups.
+ *
+ * <p>Shows a filterable list of app usage of permission groups, each of which links to
+ * AppPermissionsFragment.
+ */
+public class PermissionUsageFragment extends SettingsWithLargeHeader implements
+        PermissionUsages.PermissionsUsagesChangeCallback {
+    private static final String LOG_TAG = "PermissionUsageFragment";
+
+    @Retention(SOURCE)
+    @IntDef(value = {SORT_RECENT, SORT_RECENT_APPS})
+    @interface SortOption {}
+    static final int SORT_RECENT = 1;
+    static final int SORT_RECENT_APPS = 2;
+
+    private static final int MENU_SORT_BY_APP = MENU_HIDE_SYSTEM + 1;
+    private static final int MENU_SORT_BY_TIME = MENU_HIDE_SYSTEM + 2;
+    private static final int MENU_FILTER_BY_PERMISSIONS = MENU_HIDE_SYSTEM + 3;
+    private static final int MENU_FILTER_BY_TIME = MENU_HIDE_SYSTEM + 4;
+    private static final int MENU_REFRESH = MENU_HIDE_SYSTEM + 5;
+
+    private static final String KEY_SHOW_SYSTEM_PREFS = "_show_system";
+    private static final String SHOW_SYSTEM_KEY = PermissionUsageFragment.class.getName()
+            + KEY_SHOW_SYSTEM_PREFS;
+    private static final String KEY_PERM_NAME = "_perm_name";
+    private static final String PERM_NAME_KEY = PermissionUsageFragment.class.getName()
+            + KEY_PERM_NAME;
+    private static final String KEY_TIME_INDEX = "_time_index";
+    private static final String TIME_INDEX_KEY = PermissionUsageFragment.class.getName()
+            + KEY_TIME_INDEX;
+    private static final String KEY_SORT = "_sort";
+    private static final String SORT_KEY = PermissionUsageFragment.class.getName()
+            + KEY_SORT;
+
+    /**
+     * The maximum number of columns shown in the bar chart.
+     */
+    private static final int MAXIMUM_NUM_BARS = 4;
+
+    private @NonNull PermissionUsages mPermissionUsages;
+    private @Nullable List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>();
+
+    private Collator mCollator;
+
+    private @NonNull List<TimeFilterItem> mFilterTimes;
+    private int mFilterTimeIndex;
+    private String mFilterGroup;
+    private @SortOption int mSort;
+
+    private boolean mShowSystem;
+    private boolean mHasSystemApps;
+    private MenuItem mShowSystemMenu;
+    private MenuItem mHideSystemMenu;
+    private MenuItem mSortByApp;
+    private MenuItem mSortByTime;
+
+    private ArrayMap<String, Integer> mGroupAppCounts = new ArrayMap<>();
+
+    private boolean mFinishedInitialLoad;
+
+    /**
+     * @return A new fragment
+     */
+    public static @NonNull PermissionUsageFragment newInstance(@Nullable String groupName,
+            long numMillis) {
+        PermissionUsageFragment fragment = new PermissionUsageFragment();
+        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;
+        mSort = SORT_RECENT_APPS;
+        mFilterGroup = null;
+        initializeTimeFilter();
+        if (savedInstanceState != null) {
+            mShowSystem = savedInstanceState.getBoolean(SHOW_SYSTEM_KEY);
+            mFilterGroup = savedInstanceState.getString(PERM_NAME_KEY);
+            mFilterTimeIndex = savedInstanceState.getInt(TIME_INDEX_KEY);
+            mSort = savedInstanceState.getInt(SORT_KEY);
+        }
+
+        setLoading(true, false);
+        setHasOptionsMenu(true);
+        ActionBar ab = getActivity().getActionBar();
+        if (ab != null) {
+            ab.setDisplayHomeAsUpEnabled(true);
+        }
+
+        if (mFilterGroup == null) {
+            mFilterGroup = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
+        }
+
+        Context context = getPreferenceManager().getContext();
+        mCollator = Collator.getInstance(
+                context.getResources().getConfiguration().getLocales().get(0));
+        mPermissionUsages = new PermissionUsages(context);
+
+        reloadData();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        getActivity().setTitle(R.string.permission_usage_title);
+    }
+
+    /**
+     * 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),
+                R.string.permission_usage_list_title_any_time,
+                R.string.permission_usage_bar_chart_title_any_time));
+        mFilterTimes.add(new TimeFilterItem(DAYS.toMillis(7),
+                context.getString(R.string.permission_usage_last_7_days),
+                R.string.permission_usage_list_title_last_7_days,
+                R.string.permission_usage_bar_chart_title_last_7_days));
+        mFilterTimes.add(new TimeFilterItem(DAYS.toMillis(1),
+                context.getString(R.string.permission_usage_last_day),
+                R.string.permission_usage_list_title_last_day,
+                R.string.permission_usage_bar_chart_title_last_day));
+        mFilterTimes.add(new TimeFilterItem(HOURS.toMillis(1),
+                context.getString(R.string.permission_usage_last_hour),
+                R.string.permission_usage_list_title_last_hour,
+                R.string.permission_usage_bar_chart_title_last_hour));
+        mFilterTimes.add(new TimeFilterItem(MINUTES.toMillis(15),
+                context.getString(R.string.permission_usage_last_15_minutes),
+                R.string.permission_usage_list_title_last_15_minutes,
+                R.string.permission_usage_bar_chart_title_last_15_minutes));
+        mFilterTimes.add(new TimeFilterItem(MINUTES.toMillis(1),
+                context.getString(R.string.permission_usage_last_minute),
+                R.string.permission_usage_list_title_last_minute,
+                R.string.permission_usage_bar_chart_title_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;
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(SHOW_SYSTEM_KEY, mShowSystem);
+        outState.putString(PERM_NAME_KEY, mFilterGroup);
+        outState.putInt(TIME_INDEX_KEY, mFilterTimeIndex);
+        outState.putInt(SORT_KEY, mSort);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        mSortByApp = menu.add(Menu.NONE, MENU_SORT_BY_APP, Menu.NONE, R.string.sort_by_app);
+        mSortByTime = menu.add(Menu.NONE, MENU_SORT_BY_TIME, Menu.NONE, R.string.sort_by_time);
+        menu.add(Menu.NONE, MENU_FILTER_BY_PERMISSIONS, Menu.NONE, R.string.filter_by_permissions);
+        menu.add(Menu.NONE, MENU_FILTER_BY_TIME, Menu.NONE, R.string.filter_by_time);
+        if (mHasSystemApps) {
+            mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
+                    R.string.menu_show_system);
+            mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
+                    R.string.menu_hide_system);
+        }
+
+        HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_permission_usage,
+                getClass().getName());
+        MenuItem refresh = menu.add(Menu.NONE, MENU_REFRESH, Menu.NONE,
+                R.string.permission_usage_refresh);
+        refresh.setIcon(R.drawable.ic_refresh);
+        refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        updateMenu();
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                getActivity().finish();
+                return true;
+            case MENU_SORT_BY_APP:
+                mSort = SORT_RECENT_APPS;
+                updateUI();
+                updateMenu();
+                break;
+            case MENU_SORT_BY_TIME:
+                mSort = SORT_RECENT;
+                updateUI();
+                updateMenu();
+                break;
+            case MENU_FILTER_BY_PERMISSIONS:
+                showPermissionFilterDialog();
+                break;
+            case MENU_FILTER_BY_TIME:
+                showTimeFilterDialog();
+                break;
+            case MENU_SHOW_SYSTEM:
+            case MENU_HIDE_SYSTEM:
+                mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM;
+                // We already loaded all data, so don't reload
+                updateUI();
+                updateMenu();
+                break;
+            case MENU_REFRESH:
+                reloadData();
+                break;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateMenu() {
+        if (mHasSystemApps) {
+            /* Do not show system apps for now
+                mShowSystemMenu.setVisible(!mShowSystem);
+                mHideSystemMenu.setVisible(mShowSystem);
+             */
+            mShowSystemMenu.setVisible(false);
+            mHideSystemMenu.setVisible(false);
+        }
+
+        mSortByApp.setVisible(mSort != SORT_RECENT_APPS);
+        mSortByTime.setVisible(mSort != SORT_RECENT);
+    }
+
+    @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 int getEmptyViewString() {
+        return R.string.no_permission_usages;
+    }
+
+    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();
+
+        Preference countsWarningPreference = new Preference(getContext()) {
+            @Override
+            public void onBindViewHolder(PreferenceViewHolder holder) {
+                super.onBindViewHolder(holder);
+                ((TextView) holder.itemView.findViewById(android.R.id.title))
+                        .setTextColor(Color.RED);
+                holder.itemView.setBackgroundColor(Color.YELLOW);
+            }
+        };
+
+        StringBuffer accounts = new StringBuffer();
+        for (UserHandle user : getContext().getSystemService(UserManager.class).getAllProfiles()) {
+            for (Account account : getContext().createContextAsUser(user, 0).getSystemService(AccountManager.class).getAccounts()) {
+                accounts.append(", " + account.name);
+            }
+        }
+        if (accounts.length() > 0) {
+            accounts.delete(0, 2);
+        }
+
+        countsWarningPreference.setTitle(Html.fromHtml("<b>INTERNAL ONLY</b> - For debugging.<br/><br/>"
+                + "- Access counts do not reflect amount of private data accessed.<br/>"
+                + "- Data might not be accurate.<br/><br/>"
+                + "Accounts: " + accounts, Html.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH));
+        countsWarningPreference.setIcon(R.drawable.ic_info);
+        screen.addPreference(countsWarningPreference);
+
+        boolean seenSystemApp = false;
+
+        final TimeFilterItem timeFilterItem = mFilterTimes.get(mFilterTimeIndex);
+        long curTime = System.currentTimeMillis();
+        long startTime = Math.max(timeFilterItem == null ? 0 : (curTime - timeFilterItem.getTime()),
+                Instant.EPOCH.toEpochMilli());
+
+        List<Pair<AppPermissionUsage, GroupUsage>> usages = new ArrayList<>();
+        mGroupAppCounts.clear();
+        ArrayList<PermissionApp> permApps = new ArrayList<>();
+        int numApps = mAppPermissionUsages.size();
+        for (int appNum = 0; appNum < numApps; appNum++) {
+            AppPermissionUsage appUsage = mAppPermissionUsages.get(appNum);
+            boolean used = false;
+            List<GroupUsage> appGroups = appUsage.getGroupUsages();
+            int numGroups = appGroups.size();
+            for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+                GroupUsage groupUsage = appGroups.get(groupNum);
+                long lastAccessTime = groupUsage.getLastAccessTime();
+
+                if (groupUsage.getAccessCount() <= 0) {
+                    continue;
+                }
+                if (lastAccessTime == 0) {
+                    Log.w(LOG_TAG,
+                            "Unexpected access time of 0 for " + appUsage.getApp().getKey() + " "
+                                    + groupUsage.getGroup().getName());
+                    continue;
+                }
+                if (lastAccessTime < startTime) {
+                    continue;
+                }
+                final boolean isSystemApp = !Utils.isGroupOrBgGroupUserSensitive(
+                        groupUsage.getGroup());
+                seenSystemApp = seenSystemApp || isSystemApp;
+                if (isSystemApp && !mShowSystem) {
+                    continue;
+                }
+
+                used = true;
+                addGroupUser(groupUsage.getGroup().getName());
+
+                // Filter out usages that aren't of the filtered permission group.
+                // We do this after we call addGroupUser so we compute the correct usage counts
+                // for the permission filter dialog but before we add the usage to our list.
+                if (mFilterGroup != null && !mFilterGroup.equals(groupUsage.getGroup().getName())) {
+                    continue;
+                }
+
+                usages.add(Pair.create(appUsage, appGroups.get(groupNum)));
+            }
+            if (used) {
+                permApps.add(appUsage.getApp());
+                addGroupUser(null);
+            }
+        }
+
+        if (mHasSystemApps != seenSystemApp) {
+            mHasSystemApps = seenSystemApp;
+            getActivity().invalidateOptionsMenu();
+        }
+
+        // Update header.
+        if (mFilterGroup == null) {
+            screen.addPreference(createBarChart(usages, timeFilterItem, context));
+            hideHeader();
+        } else {
+            AppPermissionGroup group = getGroup(mFilterGroup);
+            if (group != null) {
+                setHeader(Utils.applyTint(context, context.getDrawable(group.getIconResId()),
+                        android.R.attr.colorControlNormal),
+                        context.getString(R.string.app_permission_usage_filter_label,
+                                group.getLabel()), null, null, true);
+                setSummary(context.getString(R.string.app_permission_usage_remove_filter), v -> {
+                    onPermissionGroupSelected(null);
+                });
+            }
+        }
+
+        // Add the preference header.
+        PreferenceCategory category = new PreferenceCategory(context);
+        screen.addPreference(category);
+        if (timeFilterItem != null) {
+            category.setTitle(timeFilterItem.getListTitleRes());
+        }
+
+        // Sort the apps.
+        if (mSort == SORT_RECENT) {
+            usages.sort(PermissionUsageFragment::compareAccessRecency);
+        } else if (mSort == SORT_RECENT_APPS) {
+            if (mFilterGroup == null) {
+                usages.sort(PermissionUsageFragment::compareAccessAppRecency);
+            } else {
+                usages.sort(PermissionUsageFragment::compareAccessTime);
+            }
+        } else {
+            Log.w(LOG_TAG, "Unexpected sort option: " + mSort);
+        }
+
+        // If there are no entries, don't show anything.
+        if (usages.isEmpty()) {
+            screen.removeAll();
+        }
+
+        new PermissionApps.AppDataLoader(context, () -> {
+            ExpandablePreferenceGroup parent = null;
+            AppPermissionUsage lastAppPermissionUsage = null;
+            String lastAccessTimeString = null;
+            List<CharSequence> groups = new ArrayList<>();
+
+            final int numUsages = usages.size();
+            for (int usageNum = 0; usageNum < numUsages; usageNum++) {
+                final Pair<AppPermissionUsage, GroupUsage> usage = usages.get(usageNum);
+                AppPermissionUsage appPermissionUsage = usage.first;
+                GroupUsage groupUsage = usage.second;
+
+                String accessTimeString = UtilsKt.getAbsoluteLastUsageString(context, groupUsage);
+
+                if (lastAppPermissionUsage != appPermissionUsage || (mSort == SORT_RECENT
+                        && !accessTimeString.equals(lastAccessTimeString))) {
+                    setPermissionSummary(parent, groups);
+                    // Add a "parent" entry for the app that will expand to the individual entries.
+                    parent = createExpandablePreferenceGroup(context, appPermissionUsage,
+                            mSort == SORT_RECENT ? accessTimeString : null);
+                    category.addPreference(parent);
+                    lastAppPermissionUsage = appPermissionUsage;
+                    groups = new ArrayList<>();
+                }
+
+                parent.addPreference(createPermissionUsagePreference(context, appPermissionUsage,
+                        groupUsage, accessTimeString));
+                groups.add(groupUsage.getGroup().getLabel());
+                lastAccessTimeString = accessTimeString;
+            }
+
+            setPermissionSummary(parent, groups);
+
+            setLoading(false, true);
+            mFinishedInitialLoad = true;
+            setProgressBarVisible(false);
+            mPermissionUsages.stopLoader(getActivity().getLoaderManager());
+        }).execute(permApps.toArray(new PermissionApps.PermissionApp[permApps.size()]));
+    }
+
+    private void addGroupUser(String app) {
+        Integer count = mGroupAppCounts.get(app);
+        if (count == null) {
+            mGroupAppCounts.put(app, 1);
+        } else {
+            mGroupAppCounts.put(app, count + 1);
+        }
+    }
+
+    private void setPermissionSummary(@NonNull ExpandablePreferenceGroup pref,
+            @NonNull List<CharSequence> groups) {
+        if (pref == null) {
+            return;
+        }
+        StringBuilder sb = new StringBuilder();
+        int numGroups = groups.size();
+        for (int i = 0; i < numGroups; i++) {
+            sb.append(groups.get(i));
+            if (i < numGroups - 1) {
+                sb.append(getString(R.string.item_separator));
+            }
+        }
+        pref.setSummary(sb.toString());
+    }
+
+    /**
+     * Reloads the data to show.
+     */
+    private void reloadData() {
+        final TimeFilterItem timeFilterItem = mFilterTimes.get(mFilterTimeIndex);
+        final long filterTimeBeginMillis = Math.max(System.currentTimeMillis()
+                - timeFilterItem.getTime(), Instant.EPOCH.toEpochMilli());
+        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);
+        }
+    }
+    /**
+     * Create a bar chart showing the permissions that are used by the most apps.
+     *
+     * @param usages the usages
+     * @param timeFilterItem the time filter, or null if no filter is set
+     * @param context the context
+     *
+     * @return the Preference representing the bar chart
+     */
+    private BarChartPreference createBarChart(
+            @NonNull List<Pair<AppPermissionUsage, GroupUsage>> usages,
+            @Nullable TimeFilterItem timeFilterItem, @NonNull Context context) {
+        ArrayList<AppPermissionGroup> groups = new ArrayList<>();
+        ArrayMap<String, Integer> groupToAppCount = new ArrayMap<>();
+        int usageCount = usages.size();
+        for (int i = 0; i < usageCount; i++) {
+            Pair<AppPermissionUsage, GroupUsage> usage = usages.get(i);
+            GroupUsage groupUsage = usage.second;
+            Integer count = groupToAppCount.get(groupUsage.getGroup().getName());
+            if (count == null) {
+                groups.add(groupUsage.getGroup());
+                groupToAppCount.put(groupUsage.getGroup().getName(), 1);
+            } else {
+                groupToAppCount.put(groupUsage.getGroup().getName(), count + 1);
+            }
+        }
+
+        groups.sort((x, y) -> {
+            String xName = x.getName();
+            String yName = y.getName();
+            int usageDiff = compareLong(groupToAppCount.get(xName), groupToAppCount.get(yName));
+            if (usageDiff != 0) {
+                return usageDiff;
+            }
+            if (xName.equals(LOCATION)) {
+                return -1;
+            } else if (yName.equals(LOCATION)) {
+                return 1;
+            } else if (xName.equals(MICROPHONE)) {
+                return -1;
+            } else if (yName.equals(MICROPHONE)) {
+                return 1;
+            } else if (xName.equals(CAMERA)) {
+                return -1;
+            } else if (yName.equals(CAMERA)) {
+                return 1;
+            }
+            return x.getName().compareTo(y.getName());
+        });
+
+        BarChartInfo.Builder builder = new BarChartInfo.Builder();
+        if (timeFilterItem != null) {
+            builder.setTitle(timeFilterItem.getGraphTitleRes());
+        }
+
+        int numBarsToShow = Math.min(groups.size(), MAXIMUM_NUM_BARS);
+        for (int i = 0; i < numBarsToShow; i++) {
+            AppPermissionGroup group = groups.get(i);
+            int count = groupToAppCount.get(group.getName());
+            Drawable icon = Utils.applyTint(context,
+                    Utils.loadDrawable(context.getPackageManager(), group.getIconPkg(),
+                            group.getIconResId()), android.R.attr.colorControlNormal);
+            BarViewInfo barViewInfo = new BarViewInfo(icon, count, group.getLabel(),
+                    context.getResources().getQuantityString(R.plurals.permission_usage_bar_label,
+                            count, count), group.getLabel());
+            barViewInfo.setClickListener(v -> onPermissionGroupSelected(group.getName()));
+            builder.addBarViewInfo(barViewInfo);
+        }
+
+        BarChartPreference barChart = new BarChartPreference(context, null);
+        barChart.initializeBarChart(builder.build());
+        return barChart;
+    }
+
+    /**
+     * Create an expandable preference group that can hold children.
+     *
+     * @param context the context
+     * @param appPermissionUsage the permission usage for an app
+     *
+     * @return the expandable preference group.
+     */
+    private ExpandablePreferenceGroup createExpandablePreferenceGroup(@NonNull Context context,
+            @NonNull AppPermissionUsage appPermissionUsage, @Nullable String summaryString) {
+        ExpandablePreferenceGroup preference = new ExpandablePreferenceGroup(context);
+        preference.setTitle(appPermissionUsage.getApp().getLabel());
+        preference.setIcon(appPermissionUsage.getApp().getIcon());
+        if (summaryString != null) {
+            preference.setSummary(summaryString);
+        }
+        return preference;
+    }
+
+    /**
+     * Create a preference representing an app's use of a permission
+     *
+     * @param context the context
+     * @param appPermissionUsage the permission usage for the app
+     * @param groupUsage the permission item to add
+     * @param accessTimeStr the string representing the access time
+     *
+     * @return the Preference
+     */
+    private PermissionControlPreference createPermissionUsagePreference(@NonNull Context context,
+            @NonNull AppPermissionUsage appPermissionUsage,
+            @NonNull GroupUsage groupUsage, @NonNull String accessTimeStr) {
+        final PermissionControlPreference pref = new PermissionControlPreference(context,
+                groupUsage.getGroup(), PermissionUsageFragment.class.getName());
+
+        final AppPermissionGroup group = groupUsage.getGroup();
+        pref.setTitle(group.getLabel());
+        pref.setUsageSummary(groupUsage, accessTimeStr);
+        pref.setTitleIcons(Collections.singletonList(group.getIconResId()));
+        pref.setKey(group.getApp().packageName + "," + group.getName());
+        pref.useSmallerIcon();
+        pref.setRightIcon(context.getDrawable(R.drawable.ic_settings_outline));
+        return pref;
+    }
+
+    /**
+     * Compare two usages by whichever app was used most recently.  If the two represent the same
+     * app, sort by which group was used most recently.
+     *
+     * Can be used as a {@link java.util.Comparator}.
+     *
+     * @param x a usage.
+     * @param y a usage.
+     *
+     * @return see {@link java.util.Comparator#compare(Object, Object)}.
+     */
+    private static int compareAccessAppRecency(@NonNull Pair<AppPermissionUsage, GroupUsage> x,
+            @NonNull Pair<AppPermissionUsage, GroupUsage> y) {
+        if (x.first.getApp().getKey().equals(y.first.getApp().getKey())) {
+            return compareAccessTime(x.second, y.second);
+        }
+        return compareAccessTime(x.first, y.first);
+    }
+
+    /**
+     * Compare two usages by their access time.
+     *
+     * Can be used as a {@link java.util.Comparator}.
+     *
+     * @param x a usage.
+     * @param y a usage.
+     *
+     * @return see {@link java.util.Comparator#compare(Object, Object)}.
+     */
+    private static int compareAccessTime(@NonNull Pair<AppPermissionUsage, GroupUsage> x,
+            @NonNull Pair<AppPermissionUsage, GroupUsage> y) {
+        return compareAccessTime(x.second, y.second);
+    }
+
+    /**
+     * Compare two usages by their access time.
+     *
+     * Can be used as a {@link java.util.Comparator}.
+     *
+     * @param x a usage.
+     * @param y a usage.
+     *
+     * @return see {@link java.util.Comparator#compare(Object, Object)}.
+     */
+    private static int compareAccessTime(@NonNull GroupUsage x, @NonNull GroupUsage y) {
+        final int timeDiff = compareLong(x.getLastAccessTime(), y.getLastAccessTime());
+        if (timeDiff != 0) {
+            return timeDiff;
+        }
+        // Make sure we lose no data if same
+        return x.hashCode() - y.hashCode();
+    }
+
+    /**
+     * Compare two AppPermissionUsage by their access time.
+     *
+     * Can be used as a {@link java.util.Comparator}.
+     *
+     * @param x an AppPermissionUsage.
+     * @param y an AppPermissionUsage.
+     *
+     * @return see {@link java.util.Comparator#compare(Object, Object)}.
+     */
+    private static int compareAccessTime(@NonNull AppPermissionUsage x,
+            @NonNull AppPermissionUsage y) {
+        final int timeDiff = compareLong(x.getLastAccessTime(), y.getLastAccessTime());
+        if (timeDiff != 0) {
+            return timeDiff;
+        }
+        // Make sure we lose no data if same
+        return x.hashCode() - y.hashCode();
+    }
+
+    /**
+     * Compare two longs.
+     *
+     * Can be used as a {@link java.util.Comparator}.
+     *
+     * @param x the first long.
+     * @param y the second long.
+     *
+     * @return see {@link java.util.Comparator#compare(Object, Object)}.
+     */
+    private static int compareLong(long x, long y) {
+        if (x > y) {
+            return -1;
+        } else if (x < y) {
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * Compare two usages by recency of access.
+     *
+     * Can be used as a {@link java.util.Comparator}.
+     *
+     * @param x a usage.
+     * @param y a usage.
+     *
+     * @return see {@link java.util.Comparator#compare(Object, Object)}.
+     */
+    private static int compareAccessRecency(@NonNull Pair<AppPermissionUsage, GroupUsage> x,
+            @NonNull Pair<AppPermissionUsage, GroupUsage> y) {
+        final int timeDiff = compareAccessTime(x, y);
+        if (timeDiff != 0) {
+            return timeDiff;
+        }
+        // Make sure we lose no data if same
+        return x.hashCode() - y.hashCode();
+    }
+
+    /**
+     * Get the permission groups declared by the OS.
+     *
+     * @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<GroupUsage> groupUsages = appUsage.getGroupUsages();
+            final int groupUsageCount = groupUsages.size();
+            for (int j = 0; j < groupUsageCount; j++) {
+                final GroupUsage groupUsage = groupUsages.get(j);
+                if (Utils.isModernPermissionGroup(groupUsage.getGroup().getName())) {
+                    if (seenGroups.add(groupUsage.getGroup().getName())) {
+                        groups.add(groupUsage.getGroup());
+                    }
+                }
+            }
+        }
+        return groups;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Show a dialog that allows selecting a permission group by which to filter the entries.
+     */
+    private void showPermissionFilterDialog() {
+        Context context = getPreferenceManager().getContext();
+
+        // Get the permission labels.
+        List<AppPermissionGroup> groups = getOSPermissionGroups();
+        groups.sort(
+                (x, y) -> mCollator.compare(x.getLabel().toString(), y.getLabel().toString()));
+
+        // Create the dialog entries.
+        String[] groupNames = new String[groups.size() + 1];
+        CharSequence[] groupLabels = new CharSequence[groupNames.length];
+        int[] groupAccessCounts = new int[groupNames.length];
+        groupNames[0] = null;
+        groupLabels[0] = context.getString(R.string.permission_usage_any_permission);
+        Integer allAccesses = mGroupAppCounts.get(null);
+        if (allAccesses == null) {
+            allAccesses = 0;
+        }
+        groupAccessCounts[0] = allAccesses;
+        int selection = 0;
+        int numGroups = groups.size();
+        for (int i = 0; i < numGroups; i++) {
+            AppPermissionGroup group = groups.get(i);
+            groupNames[i + 1] = group.getName();
+            groupLabels[i + 1] = group.getLabel();
+            Integer appCount = mGroupAppCounts.get(group.getName());
+            if (appCount == null) {
+                appCount = 0;
+            }
+            groupAccessCounts[i + 1] = appCount;
+            if (group.getName().equals(mFilterGroup)) {
+                selection = i + 1;
+            }
+        }
+
+        // Create the dialog
+        Bundle args = new Bundle();
+        args.putCharSequence(PermissionsFilterDialog.TITLE,
+                context.getString(R.string.filter_by_title));
+        args.putCharSequenceArray(PermissionsFilterDialog.ELEMS, groupLabels);
+        args.putInt(PermissionsFilterDialog.SELECTION, selection);
+        args.putStringArray(PermissionsFilterDialog.GROUPS, groupNames);
+        args.putIntArray(PermissionsFilterDialog.ACCESS_COUNTS, groupAccessCounts);
+        PermissionsFilterDialog chooserDialog = new PermissionsFilterDialog();
+        chooserDialog.setArguments(args);
+        chooserDialog.setTargetFragment(this, 0);
+        chooserDialog.show(getFragmentManager().beginTransaction(),
+                PermissionsFilterDialog.class.getName());
+    }
+
+    /**
+     * Callback when the user selects a permission group by which to filter.
+     *
+     * @param selectedGroup The PermissionGroup to use to filter entries, or null if we should show
+     *                      all entries.
+     */
+    private void onPermissionGroupSelected(@Nullable String selectedGroup) {
+        Fragment frag = newInstance(selectedGroup, mFilterTimes.get(mFilterTimeIndex).getTime());
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, frag)
+                .addToBackStack("PermissionUsage")
+                .commit();
+    }
+
+    /**
+     * A dialog that allows the user to select a permission group by which to filter entries.
+     *
+     * @see #showPermissionFilterDialog()
+     */
+    public static class PermissionsFilterDialog extends DialogFragment {
+        private static final String TITLE = PermissionsFilterDialog.class.getName() + ".arg.title";
+        private static final String ELEMS = PermissionsFilterDialog.class.getName() + ".arg.elems";
+        private static final String SELECTION = PermissionsFilterDialog.class.getName()
+                + ".arg.selection";
+        private static final String GROUPS = PermissionsFilterDialog.class.getName()
+                + ".arg.groups";
+        private static final String ACCESS_COUNTS = PermissionsFilterDialog.class.getName()
+                + ".arg.access_counts";
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            AlertDialog.Builder b = new AlertDialog.Builder(getActivity())
+                    .setView(createDialogView());
+
+            return b.create();
+        }
+
+        private @NonNull View createDialogView() {
+            PermissionUsageFragment fragment = (PermissionUsageFragment) getTargetFragment();
+            CharSequence[] elems = getArguments().getCharSequenceArray(ELEMS);
+            String[] groups = getArguments().getStringArray(GROUPS);
+            int[] accessCounts = getArguments().getIntArray(ACCESS_COUNTS);
+            int selectedIndex = getArguments().getInt(SELECTION);
+
+            LayoutInflater layoutInflater = LayoutInflater.from(fragment.getActivity());
+            View view = layoutInflater.inflate(R.layout.permission_filter_dialog, null);
+            ViewGroup itemsListView = view.requireViewById(R.id.items_container);
+
+            ((TextView) view.requireViewById(R.id.title)).setText(
+                    getArguments().getCharSequence(TITLE));
+
+            ActionBarShadowController.attachToView(view.requireViewById(R.id.title_container),
+                    getLifecycle(), view.requireViewById(R.id.scroll_view));
+
+            for (int i = 0; i < elems.length; i++) {
+                String groupName = groups[i];
+                View itemView = layoutInflater.inflate(R.layout.permission_filter_dialog_item,
+                        itemsListView, false);
+
+                ((TextView) itemView.requireViewById(R.id.title)).setText(elems[i]);
+                ((TextView) itemView.requireViewById(R.id.summary)).setText(
+                        getActivity().getResources().getQuantityString(
+                                R.plurals.permission_usage_permission_filter_subtitle,
+                                accessCounts[i], accessCounts[i]));
+
+                itemView.setOnClickListener((v) -> {
+                    dismissAllowingStateLoss();
+                    fragment.onPermissionGroupSelected(groupName);
+                });
+
+                RadioButton radioButton = itemView.requireViewById(R.id.radio_button);
+                radioButton.setChecked(i == selectedIndex);
+                radioButton.setOnClickListener((v) -> {
+                    dismissAllowingStateLoss();
+                    fragment.onPermissionGroupSelected(groupName);
+                });
+
+                itemsListView.addView(itemView);
+            }
+
+            return view;
+        }
+    }
+
+    private void showTimeFilterDialog() {
+        Context context = getPreferenceManager().getContext();
+
+        CharSequence[] labels = new CharSequence[mFilterTimes.size()];
+        for (int i = 0; i < labels.length; i++) {
+            labels[i] = mFilterTimes.get(i).getLabel();
+        }
+
+        // Create the dialog
+        Bundle args = new Bundle();
+        args.putCharSequence(TimeFilterDialog.TITLE,
+                context.getString(R.string.filter_by_title));
+        args.putCharSequenceArray(TimeFilterDialog.ELEMS, labels);
+        args.putInt(TimeFilterDialog.SELECTION, mFilterTimeIndex);
+        TimeFilterDialog chooserDialog = new TimeFilterDialog();
+        chooserDialog.setArguments(args);
+        chooserDialog.setTargetFragment(this, 0);
+        chooserDialog.show(getFragmentManager().beginTransaction(),
+                TimeFilterDialog.class.getName());
+    }
+
+    /**
+     * Callback when the user selects a time by which to filter.
+     *
+     * @param selectedIndex The index of the dialog option selected by the user.
+     */
+    private void onTimeSelected(int selectedIndex) {
+        mFilterTimeIndex = selectedIndex;
+        reloadData();
+    }
+
+    /**
+     * A dialog that allows the user to select a time by which to filter entries.
+     *
+     * @see #showTimeFilterDialog()
+     */
+    public static class TimeFilterDialog extends DialogFragment {
+        private static final String TITLE = TimeFilterDialog.class.getName() + ".arg.title";
+        private static final String ELEMS = TimeFilterDialog.class.getName() + ".arg.elems";
+        private static final String SELECTION = TimeFilterDialog.class.getName() + ".arg.selection";
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            PermissionUsageFragment fragment = (PermissionUsageFragment) getTargetFragment();
+            CharSequence[] elems = getArguments().getCharSequenceArray(ELEMS);
+            AlertDialog.Builder b = new AlertDialog.Builder(getActivity())
+                    .setTitle(getArguments().getCharSequence(TITLE))
+                    .setSingleChoiceItems(elems, getArguments().getInt(SELECTION),
+                            (dialog, which) -> {
+                                dismissAllowingStateLoss();
+                                fragment.onTimeSelected(which);
+                            }
+                    );
+
+            return b.create();
+        }
+    }
+
+    /**
+     * 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;
+        private final @StringRes int mListTitleRes;
+        private final @StringRes int mGraphTitleRes;
+
+        TimeFilterItem(long time, @NonNull String label, @StringRes int listTitleRes,
+                @StringRes int graphTitleRes) {
+            mTime = time;
+            mLabel = label;
+            mListTitleRes = listTitleRes;
+            mGraphTitleRes = graphTitleRes;
+        }
+
+        /**
+         * 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;
+        }
+
+        public @StringRes int getListTitleRes() {
+            return mListTitleRes;
+        }
+
+        public @StringRes int getGraphTitleRes() {
+            return mGraphTitleRes;
+        }
+    }
+}
diff --git a/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java b/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java
new file mode 100644
index 0000000..4f06132
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2020 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 android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.AppOpsManager.PackageOps;
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.Loader;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+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;
+import com.android.permissioncontroller.permission.model.legacy.PermissionGroups;
+import com.android.permissioncontroller.permission.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Loads all permission usages for a set of apps and permission groups.
+ */
+public final class PermissionUsages implements LoaderCallbacks<List<AppPermissionUsage>> {
+    public static final int USAGE_FLAG_LAST = 1 << 0;
+    public static final int USAGE_FLAG_HISTORICAL = 1 << 2;
+
+    private final ArrayList<AppPermissionUsage> mUsages = new ArrayList<>();
+    private final @NonNull Context mContext;
+
+    private static final String KEY_FILTER_UID =  "KEY_FILTER_UID";
+    private static final String KEY_FILTER_PACKAGE_NAME =  "KEY_FILTER_PACKAGE_NAME";
+    private static final String KEY_FILTER_PERMISSION_GROUP =  "KEY_FILTER_PERMISSION_GROUP";
+    private static final String KEY_FILTER_BEGIN_TIME_MILLIS =  "KEY_FILTER_BEGIN_TIME_MILLIS";
+    private static final String KEY_FILTER_END_TIME_MILLIS =  "KEY_FILTER_END_TIME_MILLIS";
+    private static final String KEY_USAGE_FLAGS =  "KEY_USAGE_FLAGS";
+    private static final String KEY_GET_UI_INFO =  "KEY_GET_UI_INFO";
+    private static final String KEY_GET_NON_PLATFORM_PERMISSIONS =
+            "KEY_GET_NON_PLATFORM_PERMISSIONS";
+
+    private @Nullable PermissionsUsagesChangeCallback mCallback;
+
+    public interface PermissionsUsagesChangeCallback {
+        void onPermissionUsagesChanged();
+    }
+
+    public PermissionUsages(@NonNull Context context) {
+        mContext = context;
+    }
+
+    public void load(@Nullable String filterPackageName,
+            @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis,
+            long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager,
+            boolean getUiInfo, boolean getNonPlatformPermissions,
+            @NonNull PermissionsUsagesChangeCallback callback, boolean sync) {
+        load(Process.INVALID_UID, filterPackageName, filterPermissionGroups, filterBeginTimeMillis,
+                filterEndTimeMillis, usageFlags, loaderManager, getUiInfo,
+                getNonPlatformPermissions, callback, sync);
+    }
+
+    public void load(int filterUid, @Nullable String filterPackageName,
+            @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis,
+            long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager,
+            boolean getUiInfo, boolean getNonPlatformPermissions,
+            @NonNull PermissionsUsagesChangeCallback callback, boolean sync) {
+        mCallback = callback;
+        final Bundle args = new Bundle();
+        args.putInt(KEY_FILTER_UID, filterUid);
+        args.putString(KEY_FILTER_PACKAGE_NAME, filterPackageName);
+        args.putStringArray(KEY_FILTER_PERMISSION_GROUP, filterPermissionGroups);
+        args.putLong(KEY_FILTER_BEGIN_TIME_MILLIS, filterBeginTimeMillis);
+        args.putLong(KEY_FILTER_END_TIME_MILLIS, filterEndTimeMillis);
+        args.putInt(KEY_USAGE_FLAGS, usageFlags);
+        args.putBoolean(KEY_GET_UI_INFO, getUiInfo);
+        args.putBoolean(KEY_GET_NON_PLATFORM_PERMISSIONS, getNonPlatformPermissions);
+        if (sync) {
+            final UsageLoader loader = new UsageLoader(mContext, args);
+            final List<AppPermissionUsage> usages = loader.loadInBackground();
+            onLoadFinished(loader, usages);
+        } else {
+            loaderManager.restartLoader(1, args, this);
+        }
+    }
+
+    @Override
+    public Loader<List<AppPermissionUsage>> onCreateLoader(int id, Bundle args) {
+        return new UsageLoader(mContext, args);
+    }
+
+    @Override
+    public void onLoadFinished(@NonNull Loader<List<AppPermissionUsage>> loader,
+            List<AppPermissionUsage> usages) {
+        mUsages.clear();
+        mUsages.addAll(usages);
+        if (mCallback != null) {
+            mCallback.onPermissionUsagesChanged();
+        }
+    }
+
+    @Override
+    public void onLoaderReset(@NonNull Loader<List<AppPermissionUsage>> loader) {
+        mUsages.clear();
+        mCallback.onPermissionUsagesChanged();
+    }
+
+    public @NonNull List<AppPermissionUsage> getUsages() {
+        return mUsages;
+    }
+
+    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;
+        private @Nullable String[] mFilterPermissionGroups;
+        private final long mFilterBeginTimeMillis;
+        private final long mFilterEndTimeMillis;
+        private final int mUsageFlags;
+        private final boolean mGetUiInfo;
+        private final boolean mGetNonPlatformPermissions;
+
+        UsageLoader(@NonNull Context context, @NonNull Bundle args) {
+            super(context);
+            mFilterUid = args.getInt(KEY_FILTER_UID);
+            mFilterPackageName = args.getString(KEY_FILTER_PACKAGE_NAME);
+            mFilterPermissionGroups = args.getStringArray(KEY_FILTER_PERMISSION_GROUP);
+            mFilterBeginTimeMillis = args.getLong(KEY_FILTER_BEGIN_TIME_MILLIS);
+            mFilterEndTimeMillis = args.getLong(KEY_FILTER_END_TIME_MILLIS);
+            mUsageFlags = args.getInt(KEY_USAGE_FLAGS);
+            mGetUiInfo = args.getBoolean(KEY_GET_UI_INFO);
+            mGetNonPlatformPermissions = args.getBoolean(KEY_GET_NON_PLATFORM_PERMISSIONS);
+        }
+
+        @Override
+        protected void onStartLoading() {
+            forceLoad();
+        }
+
+        @Override
+        public @NonNull List<AppPermissionUsage> loadInBackground() {
+            final List<PermissionGroup> groups = PermissionGroups.getPermissionGroups(
+                    getContext(), this::isLoadInBackgroundCanceled, mGetUiInfo,
+                    mGetNonPlatformPermissions, mFilterPermissionGroups, mFilterPackageName);
+            if (groups.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            final List<AppPermissionUsage> usages = new ArrayList<>();
+            final ArraySet<String> opNames = new ArraySet<>();
+            final ArrayMap<Pair<Integer, String>, AppPermissionUsage.Builder> usageBuilders =
+                    new ArrayMap<>();
+
+            final int groupCount = groups.size();
+            for (int groupIdx = 0; groupIdx < groupCount; groupIdx++) {
+                final PermissionGroup group = groups.get(groupIdx);
+                // Filter out third party permissions
+                if (!group.getDeclaringPackage().equals(Utils.OS_PKG)) {
+                    continue;
+                }
+
+                groups.add(group);
+
+                final List<PermissionApp> permissionApps = group.getPermissionApps().getApps();
+                final int appCount = permissionApps.size();
+                for (int appIdx = 0; appIdx < appCount; appIdx++) {
+                    final PermissionApp permissionApp = permissionApps.get(appIdx);
+                    if (mFilterUid != Process.INVALID_UID
+                            && permissionApp.getAppInfo().uid != mFilterUid) {
+                        continue;
+                    }
+
+                    final AppPermissionGroup appPermGroup = permissionApp.getPermissionGroup();
+                    if (!Utils.shouldShowPermission(getContext(), appPermGroup)) {
+                        continue;
+                    }
+                    final Pair<Integer, String> usageKey = Pair.create(permissionApp.getUid(),
+                            permissionApp.getPackageName());
+                    AppPermissionUsage.Builder usageBuilder = usageBuilders.get(usageKey);
+                    if (usageBuilder == null) {
+                        usageBuilder = new Builder(permissionApp);
+                        usageBuilders.put(usageKey, usageBuilder);
+                    }
+                    usageBuilder.addGroup(appPermGroup);
+                    final List<Permission> permissions = appPermGroup.getPermissions();
+                    final int permCount = permissions.size();
+                    for (int permIdx = 0; permIdx < permCount; permIdx++) {
+                        final Permission permission = permissions.get(permIdx);
+                        final String opName = permission.getAppOp();
+                        if (opName != null) {
+                            opNames.add(opName);
+                        }
+                    }
+                }
+            }
+
+            if (usageBuilders.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+
+            // Get last usage data and put in a map for a quick lookup.
+            final ArrayMap<Pair<Integer, String>, PackageOps> lastUsages =
+                    new ArrayMap<>(usageBuilders.size());
+            final String[] opNamesArray = opNames.toArray(new String[opNames.size()]);
+            if ((mUsageFlags & USAGE_FLAG_LAST) != 0) {
+                final List<PackageOps> usageOps;
+                if (mFilterPackageName != null || mFilterUid != Process.INVALID_UID) {
+                    usageOps = appOpsManager.getOpsForPackage(mFilterUid, mFilterPackageName,
+                            opNamesArray);
+                } else {
+                    usageOps = appOpsManager.getPackagesForOps(opNamesArray);
+                }
+                if (usageOps != null && !usageOps.isEmpty()) {
+                    final int usageOpsCount = usageOps.size();
+                    for (int i = 0; i < usageOpsCount; i++) {
+                        final PackageOps usageOp = usageOps.get(i);
+                        lastUsages.put(Pair.create(usageOp.getUid(), usageOp.getPackageName()),
+                                usageOp);
+                    }
+                }
+            }
+
+            if (isLoadInBackgroundCanceled()) {
+                return Collections.emptyList();
+            }
+
+            // Get historical usage data and put in a map for a quick lookup
+            final ArrayMap<Pair<Integer, String>, HistoricalPackageOps> historicalUsages =
+                    new ArrayMap<>(usageBuilders.size());
+            if ((mUsageFlags & USAGE_FLAG_HISTORICAL) != 0) {
+                final AtomicReference<HistoricalOps> historicalOpsRef = new AtomicReference<>();
+                final CountDownLatch latch = new CountDownLatch(1);
+                final HistoricalOpsRequest request = new HistoricalOpsRequest.Builder(
+                        mFilterBeginTimeMillis, mFilterEndTimeMillis)
+                        .setUid(mFilterUid)
+                        .setPackageName(mFilterPackageName)
+                        .setOpNames(new ArrayList<>(opNames))
+                        .setFlags(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+                        .build();
+                appOpsManager.getHistoricalOps(request, Runnable::run,
+                        (HistoricalOps ops) -> {
+                            historicalOpsRef.set(ops);
+                            latch.countDown();
+                        });
+                try {
+                    latch.await(5, TimeUnit.DAYS);
+                } catch (InterruptedException ignored) { }
+
+                final HistoricalOps historicalOps = historicalOpsRef.get();
+                if (historicalOps != null) {
+                    final int uidCount = historicalOps.getUidCount();
+                    for (int i = 0; i < uidCount; i++) {
+                        final HistoricalUidOps uidOps = historicalOps.getUidOpsAt(i);
+                        final int packageCount = uidOps.getPackageCount();
+                        for (int j = 0; j < packageCount; j++) {
+                            final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(j);
+                            historicalUsages.put(
+                                    Pair.create(uidOps.getUid(), packageOps.getPackageName()),
+                                    packageOps);
+                        }
+                    }
+                }
+            }
+
+            // Get audio recording config
+            List<AudioRecordingConfiguration> allRecordings = getContext()
+                    .getSystemService(AudioManager.class).getActiveRecordingConfigurations();
+            SparseArray<ArrayList<AudioRecordingConfiguration>> recordingsByUid =
+                    new SparseArray<>();
+
+            final int recordingsCount = allRecordings.size();
+            for (int i = 0; i < recordingsCount; i++) {
+                AudioRecordingConfiguration recording = allRecordings.get(i);
+
+                ArrayList<AudioRecordingConfiguration> recordings = recordingsByUid.get(
+                        recording.getClientUid());
+                if (recordings == null) {
+                    recordings = new ArrayList<>();
+                    recordingsByUid.put(recording.getClientUid(), recordings);
+                }
+                recordings.add(recording);
+            }
+
+            // Construct the historical usages based on data we fetched
+            final int builderCount = usageBuilders.size();
+            for (int i = 0; i < builderCount; i++) {
+                final Pair<Integer, String> key = usageBuilders.keyAt(i);
+                final Builder usageBuilder = usageBuilders.valueAt(i);
+                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());
+            }
+
+            return usages;
+        }
+    }
+}
diff --git a/src/com/android/permissioncontroller/permission/debug/PreferenceImageView.java b/src/com/android/permissioncontroller/permission/debug/PreferenceImageView.java
new file mode 100644
index 0000000..378601d
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/PreferenceImageView.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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 android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * Extension of ImageView that correctly applies maxWidth and maxHeight.
+ */
+public class PreferenceImageView extends ImageView {
+
+    public PreferenceImageView(Context context) {
+        this(context, null);
+    }
+
+    public PreferenceImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PreferenceImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public PreferenceImageView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
+            final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+            final int maxWidth = getMaxWidth();
+            if (maxWidth != Integer.MAX_VALUE
+                    && (maxWidth < widthSize || widthMode == MeasureSpec.UNSPECIFIED)) {
+                widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST);
+            }
+        }
+
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
+            final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+            final int maxHeight = getMaxHeight();
+            if (maxHeight != Integer.MAX_VALUE
+                    && (maxHeight < heightSize || heightMode == MeasureSpec.UNSPECIFIED)) {
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
+            }
+        }
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+}
diff --git a/src/com/android/permissioncontroller/permission/debug/Utils.kt b/src/com/android/permissioncontroller/permission/debug/Utils.kt
new file mode 100644
index 0000000..0c01b90
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/Utils.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2020 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 android.content.Context
+import android.icu.util.Calendar
+import android.provider.DeviceConfig
+import android.text.format.DateFormat.getMediumDateFormat
+import android.text.format.DateFormat.getTimeFormat
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.model.AppPermissionUsage.GroupUsage
+import java.util.Locale
+
+/** Whether to show the Permissions Hub.  */
+private const val PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"
+
+/** Whether to show the mic and camera icons.  */
+const val PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled"
+
+/**
+ * Whether the Permissions Hub 2 flag is enabled
+ *
+ * @return whether the flag is enabled
+ */
+fun isPermissionsHub2FlagEnabled(): Boolean {
+    return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+        PROPERTY_PERMISSIONS_HUB_2_ENABLED, false)
+}
+/**
+ * Whether to show the Permissions Dashboard
+ *
+ * @return whether to show the Permissions Dashboard.
+ */
+fun shouldShowPermissionsDashboard(): Boolean {
+    return isPermissionsHub2FlagEnabled()
+}
+
+/**
+ * Whether the Camera and Mic Icons are enabled by flag.
+ *
+ * @return whether the Camera and Mic Icons are enabled.
+ */
+fun isCameraMicIconsFlagEnabled(): Boolean {
+    return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+        PROPERTY_CAMERA_MIC_ICONS_ENABLED, true)
+}
+
+/**
+ * Whether to show Camera and Mic Icons. They should be shown if the permission hub, or the icons
+ * specifically, are enabled.
+ *
+ * @return whether to show the icons.
+ */
+fun shouldShowCameraMicIndicators(): Boolean {
+    return isCameraMicIconsFlagEnabled() || isPermissionsHub2FlagEnabled()
+}
+
+/**
+ * Build a string representing the given time if it happened on the current day and the date
+ * otherwise.
+ *
+ * @param context the context.
+ * @param lastAccessTime the time in milliseconds.
+ *
+ * @return a string representing the time or date of the given time or null if the time is 0.
+ */
+fun getAbsoluteTimeString(context: Context, lastAccessTime: Long): String? {
+    if (lastAccessTime == 0L) {
+        return null
+    }
+    return if (isToday(lastAccessTime)) {
+        getTimeFormat(context).format(lastAccessTime)
+    } else {
+        getMediumDateFormat(context).format(lastAccessTime)
+    }
+}
+
+/**
+ * Build a string representing the time of the most recent permission usage if it happened on
+ * the current day and the date otherwise.
+ *
+ * @param context the context.
+ * @param groupUsage the permission usage.
+ *
+ * @return a string representing the time or date of the most recent usage or null if there are
+ * no usages.
+ */
+fun getAbsoluteLastUsageString(context: Context, groupUsage: GroupUsage?): String? {
+    return if (groupUsage == null) {
+        null
+    } else getAbsoluteTimeString(context, groupUsage.lastAccessTime)
+}
+
+/**
+ * Build a string representing the duration of a permission usage.
+ *
+ * @return a string representing the duration of this app's usage or null if there are no
+ * usages.
+ */
+fun getUsageDurationString(context: Context, groupUsage: GroupUsage?): String? {
+    return if (groupUsage == null) {
+        null
+    } else getTimeDiffStr(context, groupUsage.accessDuration)
+}
+
+/**
+ * Build a string representing the number of milliseconds passed in.  It rounds to the nearest
+ * unit.  For example, given a duration of 3500 and an English locale, this can return
+ * "3 seconds".
+ * @param context The context.
+ * @param duration The number of milliseconds.
+ * @return a string representing the given number of milliseconds.
+ */
+fun getTimeDiffStr(context: Context, duration: Long): String {
+    val seconds = Math.max(1, duration / 1000)
+    if (seconds < 60) {
+        return context.resources.getQuantityString(R.plurals.seconds, seconds.toInt(),
+                seconds)
+    }
+    val minutes = seconds / 60
+    if (minutes < 60) {
+        return context.resources.getQuantityString(R.plurals.minutes, minutes.toInt(),
+                minutes)
+    }
+    val hours = minutes / 60
+    if (hours < 24) {
+        return context.resources.getQuantityString(R.plurals.hours, hours.toInt(), hours)
+    }
+    val days = hours / 24
+    return context.resources.getQuantityString(R.plurals.days, days.toInt(), days)
+}
+
+/**
+ * Check whether the given time (in milliseconds) is in the current day.
+ *
+ * @param time the time in milliseconds
+ *
+ * @return whether the given time is in the current day.
+ */
+private fun isToday(time: Long): Boolean {
+    val today: Calendar = Calendar.getInstance(Locale.getDefault())
+    today.setTimeInMillis(System.currentTimeMillis())
+    today.set(Calendar.HOUR_OF_DAY, 0)
+    today.set(Calendar.MINUTE, 0)
+    today.set(Calendar.SECOND, 0)
+    today.set(Calendar.MILLISECOND, 0)
+    val date: Calendar = Calendar.getInstance(Locale.getDefault())
+    date.setTimeInMillis(time)
+    return !date.before(today)
+}
\ No newline at end of file
diff --git a/src/com/android/permissioncontroller/permission/model/AppPermissionUsage.java b/src/com/android/permissioncontroller/permission/model/AppPermissionUsage.java
new file mode 100644
index 0000000..d45e9b4
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/model/AppPermissionUsage.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2020 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.model;
+
+import static android.Manifest.permission_group.MICROPHONE;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOp;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+import android.media.AudioRecordingConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Stats for permission usage of an app. This data is for a given time period,
+ * i.e. does not contain the full history.
+ */
+public final class AppPermissionUsage {
+    private final @NonNull List<GroupUsage> mGroupUsages = new ArrayList<>();
+    private final @NonNull PermissionApp mPermissionApp;
+
+    private AppPermissionUsage(@NonNull PermissionApp permissionApp,
+            @NonNull List<AppPermissionGroup> groups, @Nullable PackageOps lastUsage,
+            @Nullable HistoricalPackageOps historicalUsage,
+            @Nullable ArrayList<AudioRecordingConfiguration> recordings) {
+        mPermissionApp = permissionApp;
+        final int groupCount = groups.size();
+        for (int i = 0; i < groupCount; i++) {
+            final AppPermissionGroup group = groups.get(i);
+
+            /**
+             * TODO: HACK HACK HACK.
+             *
+             * Exclude for the UIDs that are currently silenced. This happens if an app keeps
+             * recording while in the background for more than a few seconds.
+             */
+            if (recordings != null && group.getName().equals(MICROPHONE)) {
+                boolean isSilenced = false;
+                int recordingsCount = recordings.size();
+                for (int recordingNum = 0; recordingNum < recordingsCount; recordingNum++) {
+                    AudioRecordingConfiguration recording = recordings.get(recordingNum);
+                    if (recording.isClientSilenced()) {
+                        isSilenced = true;
+                        break;
+                    }
+                }
+
+                if (isSilenced) {
+                    continue;
+                }
+            }
+
+            mGroupUsages.add(new GroupUsage(group, lastUsage, historicalUsage));
+        }
+    }
+
+    public @NonNull PermissionApp getApp() {
+        return mPermissionApp;
+    }
+
+    public @NonNull String getPackageName() {
+        return mPermissionApp.getPackageName();
+    }
+
+    public int getUid() {
+        return mPermissionApp.getUid();
+    }
+
+    public long getLastAccessTime() {
+        long lastAccessTime = 0;
+        final int permissionCount = mGroupUsages.size();
+        for (int i = 0; i < permissionCount; i++) {
+            final GroupUsage groupUsage = mGroupUsages.get(i);
+            lastAccessTime = Math.max(lastAccessTime, groupUsage.getLastAccessTime());
+        }
+        return lastAccessTime;
+    }
+
+    public long getAccessCount() {
+        long accessCount = 0;
+        final int permissionCount = mGroupUsages.size();
+        for (int i = 0; i < permissionCount; i++) {
+            final GroupUsage permission = mGroupUsages.get(i);
+            accessCount += permission.getAccessCount();
+        }
+        return accessCount;
+    }
+
+    public @NonNull List<GroupUsage> getGroupUsages() {
+        return mGroupUsages;
+    }
+
+    /**
+     * Stats for permission usage of a permission group. This data is for a
+     * given time period, i.e. does not contain the full history.
+     */
+    public static class GroupUsage {
+        private final @NonNull AppPermissionGroup mGroup;
+        private final @Nullable PackageOps mLastUsage;
+        private final @Nullable HistoricalPackageOps mHistoricalUsage;
+
+        public GroupUsage(@NonNull AppPermissionGroup group, @Nullable PackageOps lastUsage,
+                @Nullable HistoricalPackageOps historicalUsage) {
+            mGroup = group;
+            mLastUsage = lastUsage;
+            mHistoricalUsage = historicalUsage;
+        }
+
+        public long getLastAccessTime() {
+            if (mLastUsage == null) {
+                return 0;
+            }
+            return lastAccessAggregate(
+                    (op) -> op.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+        }
+
+        public long getLastAccessForegroundTime() {
+            if (mLastUsage == null) {
+                return 0;
+            }
+            return lastAccessAggregate(
+                    (op) -> op.getLastAccessForegroundTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+        }
+
+        public long getLastAccessBackgroundTime() {
+            if (mLastUsage == null) {
+                return 0;
+            }
+            return lastAccessAggregate(
+                    (op) -> op.getLastAccessBackgroundTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+        }
+
+        public long getForegroundAccessCount() {
+            if (mHistoricalUsage == null) {
+                return 0;
+            }
+            return extractAggregate((HistoricalOp op)
+                    -> op.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+        }
+
+        public long getBackgroundAccessCount() {
+            if (mHistoricalUsage == null) {
+                return 0;
+            }
+            return extractAggregate((HistoricalOp op)
+                    -> op.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+        }
+
+        public long getAccessCount() {
+            if (mHistoricalUsage == null) {
+                return 0;
+            }
+            return extractAggregate((HistoricalOp op) ->
+                    op.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+                            + op.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+            );
+        }
+
+        public long getLastAccessDuration() {
+            if (mLastUsage == null) {
+                return 0;
+            }
+            return lastAccessAggregate(
+                    (op) -> op.getLastDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+        }
+
+
+        public long getAccessDuration() {
+            if (mHistoricalUsage == null) {
+                return 0;
+            }
+            return extractAggregate((HistoricalOp op) ->
+                    op.getForegroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+                            + op.getBackgroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+            );
+        }
+
+        public boolean isRunning() {
+            if (mLastUsage == 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 List<OpEntry> ops = mLastUsage.getOps();
+                final int opCount = ops.size();
+                for (int j = 0; j < opCount; j++) {
+                    final OpEntry op = ops.get(j);
+                    if (op.getOpStr().equals(opName) && op.isRunning()) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        private long extractAggregate(@NonNull Function<HistoricalOp, Long> extractor) {
+            long aggregate = 0;
+            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) {
+                    aggregate += extractor.apply(historicalOp);
+                }
+            }
+            return aggregate;
+        }
+
+        private long lastAccessAggregate(@NonNull Function<OpEntry, Long> extractor) {
+            long aggregate = 0;
+            final ArrayList<Permission> permissions = mGroup.getPermissions();
+            final int permissionCount = permissions.size();
+            for (int permissionNum = 0; permissionNum < permissionCount; permissionNum++) {
+                final Permission permission = permissions.get(permissionNum);
+                final String opName = permission.getAppOp();
+                final List<OpEntry> ops = mLastUsage.getOps();
+                final int opCount = ops.size();
+                for (int opNum = 0; opNum < opCount; opNum++) {
+                    final OpEntry op = ops.get(opNum);
+                    if (op.getOpStr().equals(opName)) {
+                        aggregate = Math.max(aggregate, extractor.apply(op));
+                    }
+                }
+            }
+            return aggregate;
+        }
+
+        public @NonNull AppPermissionGroup getGroup() {
+            return mGroup;
+        }
+    }
+
+    public static class Builder {
+        private final @NonNull List<AppPermissionGroup> mGroups = new ArrayList<>();
+        private final @NonNull PermissionApp mPermissionApp;
+        private @Nullable PackageOps mLastUsage;
+        private @Nullable HistoricalPackageOps mHistoricalUsage;
+        private @Nullable ArrayList<AudioRecordingConfiguration> mAudioRecordingConfigurations;
+
+        public Builder(@NonNull PermissionApp permissionApp) {
+            mPermissionApp = permissionApp;
+        }
+
+        public @NonNull Builder addGroup(@NonNull AppPermissionGroup group) {
+            mGroups.add(group);
+            return this;
+        }
+
+        public @NonNull Builder setLastUsage(@Nullable PackageOps lastUsage) {
+            mLastUsage = lastUsage;
+            return this;
+        }
+
+        public @NonNull Builder setHistoricalUsage(@Nullable HistoricalPackageOps historicalUsage) {
+            mHistoricalUsage = historicalUsage;
+            return this;
+        }
+
+        public @NonNull Builder setRecordingConfiguration(
+                @Nullable ArrayList<AudioRecordingConfiguration> recordings) {
+            mAudioRecordingConfigurations = recordings;
+            return this;
+        }
+
+        public @NonNull AppPermissionUsage build() {
+            if (mGroups.isEmpty()) {
+                throw new IllegalStateException("mGroups cannot be empty.");
+            }
+            return new AppPermissionUsage(mPermissionApp, mGroups, mLastUsage, mHistoricalUsage,
+                    mAudioRecordingConfigurations);
+        }
+    }
+}
diff --git a/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java b/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java
index e837ade..e493e08 100644
--- a/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java
+++ b/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java
@@ -421,6 +421,19 @@
             return mAppPermissionGroup;
         }
 
+        /**
+         * Load this app's label and icon if they were not previously loaded.
+         *
+         * @param appDataCache the cache of already-loaded labels and icons.
+         */
+        public void loadLabelAndIcon(@NonNull AppDataCache appDataCache) {
+            if (mInfo.packageName.equals(mLabel) || mIcon == null) {
+                Pair<String, Drawable> appData = appDataCache.getAppData(getUid(), mInfo);
+                mLabel = appData.first;
+                mIcon = appData.second;
+            }
+        }
+
         @Override
         public int compareTo(PermissionApp another) {
             final int result = mLabel.compareTo(another.mLabel);
@@ -520,4 +533,33 @@
     public interface Callback {
         void onPermissionsLoaded(PermissionApps permissionApps);
     }
+
+    /**
+     * Class used to asynchronously load apps' labels and icons.
+     */
+    public static class AppDataLoader extends AsyncTask<PermissionApp, Void, Void> {
+
+        private final Context mContext;
+        private final Runnable mCallback;
+
+        public AppDataLoader(Context context, Runnable callback) {
+            mContext = context;
+            mCallback = callback;
+        }
+
+        @Override
+        protected Void doInBackground(PermissionApp... args) {
+            AppDataCache appDataCache = new AppDataCache(mContext.getPackageManager(), mContext);
+            int numArgs = args.length;
+            for (int i = 0; i < numArgs; i++) {
+                args[i].loadLabelAndIcon(appDataCache);
+            }
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void result) {
+            mCallback.run();
+        }
+    }
 }
diff --git a/src/com/android/permissioncontroller/permission/model/legacy/PermissionGroup.java b/src/com/android/permissioncontroller/permission/model/legacy/PermissionGroup.java
index da1039d..6b951c9 100644
--- a/src/com/android/permissioncontroller/permission/model/legacy/PermissionGroup.java
+++ b/src/com/android/permissioncontroller/permission/model/legacy/PermissionGroup.java
@@ -78,6 +78,13 @@
         return mGranted;
     }
 
+    /**
+     * @return The PermissionApps object for this permission group.
+     */
+    public PermissionApps getPermissionApps() {
+        return mPermApps;
+    }
+
     @Override
     public int compareTo(PermissionGroup another) {
         return mLabel.toString().compareTo(another.mLabel.toString());
diff --git a/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index 04336df..7297792 100644
--- a/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -46,6 +46,8 @@
 import com.android.permissioncontroller.DeviceUtils;
 import com.android.permissioncontroller.PermissionControllerStatsLog;
 import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.debug.PermissionUsageFragment;
+import com.android.permissioncontroller.permission.debug.UtilsKt;
 import com.android.permissioncontroller.permission.ui.auto.AutoAllAppPermissionsFragment;
 import com.android.permissioncontroller.permission.ui.auto.AutoAppPermissionsFragment;
 import com.android.permissioncontroller.permission.ui.auto.AutoManageStandardPermissionsFragment;
@@ -141,6 +143,16 @@
                 }
                 break;
 
+            case Intent.ACTION_REVIEW_PERMISSION_USAGE: {
+                if (!UtilsKt.shouldShowPermissionsDashboard()) {
+                    finish();
+                    return;
+                }
+
+                String groupName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME);
+                androidXFragment = PermissionUsageFragment.newInstance(groupName, Long.MAX_VALUE);
+            } break;
+
             case Intent.ACTION_MANAGE_APP_PERMISSION: {
                 if (DeviceUtils.isAuto(this) || DeviceUtils.isTelevision(this)
                         || DeviceUtils.isWear(this)) {
diff --git a/src/com/android/permissioncontroller/permission/ui/ReviewOngoingUsageActivity.java b/src/com/android/permissioncontroller/permission/ui/ReviewOngoingUsageActivity.java
new file mode 100644
index 0000000..2082b76
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/ui/ReviewOngoingUsageActivity.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.permissioncontroller.DeviceUtils;
+import com.android.permissioncontroller.permission.ui.handheld.ReviewOngoingUsageFragment;
+import com.android.permissioncontroller.permission.debug.UtilsKt;
+
+/**
+ * A dialog listing the currently uses of camera, microphone, and location.
+ */
+public final class ReviewOngoingUsageActivity extends FragmentActivity {
+
+    // Number of milliseconds in the past to look for accesses if nothing was specified.
+    private static final long DEFAULT_MILLIS = 5000;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (!UtilsKt.shouldShowCameraMicIndicators()) {
+            finish();
+            return;
+        }
+
+        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+        long numMillis = getIntent().getLongExtra(Intent.EXTRA_DURATION_MILLIS, DEFAULT_MILLIS);
+        getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+                ReviewOngoingUsageFragment.newInstance(numMillis)).commit();
+    }
+
+
+    @Override
+    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                // in automotive mode, there's no system wide back button, so need to add that
+                if (DeviceUtils.isAuto(this)) {
+                    onBackPressed();
+                } else {
+                    finish();
+                }
+                return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java b/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
index e3f0f98..cd8c27f 100644
--- a/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
+++ b/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
@@ -81,6 +81,9 @@
     private static final String AUTO_REVOKE_CATEGORY_KEY = "_AUTO_REVOKE_KEY";
     private static final String AUTO_REVOKE_SWITCH_KEY = "_AUTO_REVOKE_SWITCH_KEY";
     private static final String AUTO_REVOKE_SUMMARY_KEY = "_AUTO_REVOKE_SUMMARY_KEY";
+    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";
 
     static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
 
@@ -346,10 +349,12 @@
         Preference autoRevokeSummary = autoRevokeCategory.findPreference(AUTO_REVOKE_SUMMARY_KEY);
 
         if (!state.isEnabledGlobal() || !state.getShouldShowSwitch()) {
+            autoRevokeCategory.setVisible(false);
             autoRevokeSwitch.setVisible(false);
             autoRevokeSummary.setVisible(false);
             return;
         }
+        autoRevokeCategory.setVisible(true);
         autoRevokeSwitch.setVisible(true);
         autoRevokeSummary.setVisible(true);
         autoRevokeSwitch.setChecked(state.isEnabledForApp());
diff --git a/src/com/android/permissioncontroller/permission/ui/handheld/AutoRevokeFragment.kt b/src/com/android/permissioncontroller/permission/ui/handheld/AutoRevokeFragment.kt
index f423555..e1364f3 100644
--- a/src/com/android/permissioncontroller/permission/ui/handheld/AutoRevokeFragment.kt
+++ b/src/com/android/permissioncontroller/permission/ui/handheld/AutoRevokeFragment.kt
@@ -39,6 +39,7 @@
 import com.android.permissioncontroller.permission.ui.model.AutoRevokeViewModelFactory
 import com.android.permissioncontroller.permission.utils.IPC
 import com.android.permissioncontroller.permission.utils.KotlinUtils
+import kotlinx.coroutines.Dispatchers.Main
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
@@ -105,7 +106,9 @@
             GlobalScope.launch(IPC) {
                 delay(SHOW_LOAD_DELAY_MS)
                 if (!viewModel.areAutoRevokedPackagesLoaded()) {
-                    setLoading(true, false)
+                    GlobalScope.launch(Main) {
+                        setLoading(true, true)
+                    }
                 }
             }
         }
diff --git a/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java b/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
index 261f82e..ddae029 100644
--- a/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
+++ b/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
@@ -17,10 +17,14 @@
 
 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
+import static com.android.permissioncontroller.permission.debug.UtilsKt.shouldShowPermissionsDashboard;
 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
 
+import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.MenuItem;
 
 import androidx.lifecycle.ViewModelProvider;
@@ -28,6 +32,7 @@
 import androidx.preference.PreferenceScreen;
 
 import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
 import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel;
 import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModelFactory;
 import com.android.permissioncontroller.permission.utils.Utils;
@@ -40,6 +45,8 @@
     private static final String AUTO_REVOKE_KEY = "auto_revoke_key";
     private static final String LOG_TAG = ManageStandardPermissionsFragment.class.getSimpleName();
 
+    private static final int MENU_PERMISSION_USAGE = MENU_HIDE_SYSTEM + 1;
+
     private ManageStandardPermissionsViewModel mViewModel;
 
     /**
@@ -87,14 +94,28 @@
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        if (item.getItemId() == android.R.id.home) {
-            pressBack(this);
-            return true;
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                pressBack(this);
+                return true;
+            case MENU_PERMISSION_USAGE:
+                getActivity().startActivity(new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
+                        .setClass(getContext(), ManagePermissionsActivity.class));
+                return true;
         }
         return super.onOptionsItemSelected(item);
     }
 
     @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+
+        if (shouldShowPermissionsDashboard()) {
+            menu.add(Menu.NONE, MENU_PERMISSION_USAGE, Menu.NONE, R.string.permission_usage_title);
+        }
+    }
+
+    @Override
     protected PreferenceScreen updatePermissionsUi() {
         PreferenceScreen screen = super.updatePermissionsUi();
         if (screen == null) {
diff --git a/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java b/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
index b73b426..7c9c46b 100644
--- a/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
+++ b/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
@@ -22,6 +22,7 @@
 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.debug.UtilsKt.shouldShowPermissionsDashboard;
 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;
@@ -52,6 +53,7 @@
 import com.android.permissioncontroller.PermissionControllerStatsLog;
 import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.permission.ui.Category;
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel;
 import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModelFactory;
 import com.android.permissioncontroller.permission.utils.KotlinUtils;
@@ -82,6 +84,8 @@
     private static final String STORAGE_ALLOWED_SCOPED = "allowed_storage_scoped";
     private static final int SHOW_LOAD_DELAY_MS = 200;
 
+    private static final int MENU_PERMISSION_USAGE = MENU_HIDE_SYSTEM + 1;
+
     /**
      * Create a bundle with the arguments needed by this fragment
      *
@@ -152,6 +156,10 @@
             updateMenu(mViewModel.getShouldShowSystemLiveData().getValue());
         }
 
+        if (shouldShowPermissionsDashboard()) {
+            menu.add(Menu.NONE, MENU_PERMISSION_USAGE, Menu.NONE, R.string.permission_usage_title);
+        }
+
         HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_app_permissions,
                 getClass().getName());
     }
@@ -167,6 +175,11 @@
             case MENU_HIDE_SYSTEM:
                 mViewModel.updateShowSystem(item.getItemId() == MENU_SHOW_SYSTEM);
                 break;
+            case MENU_PERMISSION_USAGE:
+                getActivity().startActivity(new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
+                        .setClass(getContext(), ManagePermissionsActivity.class)
+                        .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermGroupName));
+                return true;
         }
         return super.onOptionsItemSelected(item);
     }
diff --git a/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java b/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java
index 60dfbe0..fe6ee35 100644
--- a/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java
+++ b/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java
@@ -16,7 +16,11 @@
 
 package com.android.permissioncontroller.permission.ui.handheld;
 
+import static android.Manifest.permission_group.CAMERA;
+import static android.Manifest.permission_group.MICROPHONE;
+
 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
+import static com.android.permissioncontroller.permission.debug.UtilsKt.getUsageDurationString;
 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
 import static com.android.permissioncontroller.permission.ui.handheld.AppPermissionFragment.GRANT_CATEGORY;
 import static com.android.permissioncontroller.permission.utils.KotlinUtilsKt.navigateSafe;
@@ -39,8 +43,9 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.permission.model.AppPermissionGroup;
+import com.android.permissioncontroller.permission.model.AppPermissionUsage.GroupUsage;
+import com.android.permissioncontroller.R;
 import com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog;
 import com.android.permissioncontroller.permission.utils.LocationUtils;
 
@@ -95,6 +100,18 @@
     }
 
     /**
+     * Sets this preference's right icon.
+     *
+     * Note that this must be called before preference layout to take effect.
+     *
+     * @param widgetIcon the icon to use.
+     */
+    public void setRightIcon(@NonNull Drawable widgetIcon) {
+        mWidgetIcon = widgetIcon;
+        setWidgetLayoutResource(R.layout.image_view);
+    }
+
+    /**
      * Sets this preference's left icon to be smaller than normal.
      *
      * Note that this must be called before preference layout to take effect.
@@ -128,6 +145,56 @@
         setSummary("");
     }
 
+    /**
+     * Sets this preference's summary based on its permission usage.
+     *
+     * @param groupUsage the usage information
+     * @param accessTimeStr the string representing the last access time
+     */
+    public void setUsageSummary(@NonNull GroupUsage groupUsage, @NonNull String accessTimeStr) {
+        long backgroundAccessCount = groupUsage.getBackgroundAccessCount();
+        long duration = 0;
+        String groupName = groupUsage.getGroup().getName();
+        if (groupName.equals(CAMERA) || groupName.equals(MICROPHONE)) {
+            duration = groupUsage.getAccessDuration();
+        }
+        if (backgroundAccessCount == 0) {
+            long numForegroundAccesses = groupUsage.getForegroundAccessCount();
+            if (duration == 0) {
+                setSummary(mContext.getResources().getQuantityString(
+                        R.plurals.permission_usage_summary, (int) numForegroundAccesses,
+                        accessTimeStr, numForegroundAccesses));
+            } else {
+                setSummary(mContext.getResources().getQuantityString(
+                        R.plurals.permission_usage_summary_duration, (int) numForegroundAccesses,
+                        accessTimeStr, numForegroundAccesses,
+                        getUsageDurationString(mContext, groupUsage)));
+            }
+        } else {
+            long numAccesses = groupUsage.getAccessCount();
+            if (duration == 0) {
+                setSummary(mContext.getResources().getQuantityString(
+                        R.plurals.permission_usage_summary_background, (int) numAccesses,
+                        accessTimeStr, numAccesses, backgroundAccessCount));
+            } else {
+                setSummary(mContext.getResources().getQuantityString(
+                        R.plurals.permission_usage_summary_background_duration, (int) numAccesses,
+                        accessTimeStr, numAccesses, backgroundAccessCount,
+                        getUsageDurationString(mContext, groupUsage)));
+            }
+        }
+    }
+
+    /**
+     * Sets this preference to show the given icons to the left of its title.
+     *
+     * @param titleIcons the icons to show.
+     */
+    public void setTitleIcons(@NonNull List<Integer> titleIcons) {
+        mTitleIcons = titleIcons;
+        setLayoutResource(R.layout.preference_usage);
+    }
+
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         if (mUseSmallerIcon) {
diff --git a/src/com/android/permissioncontroller/permission/ui/handheld/PermissionsFrameFragment.java b/src/com/android/permissioncontroller/permission/ui/handheld/PermissionsFrameFragment.java
index 78fd84e..ccbbd26 100644
--- a/src/com/android/permissioncontroller/permission/ui/handheld/PermissionsFrameFragment.java
+++ b/src/com/android/permissioncontroller/permission/ui/handheld/PermissionsFrameFragment.java
@@ -39,8 +39,8 @@
     private static final String LOG_TAG = PermissionsFrameFragment.class.getSimpleName();
 
     static final int MENU_ALL_PERMS = Menu.FIRST + 1;
-    static final int MENU_SHOW_SYSTEM = Menu.FIRST + 2;
-    static final int MENU_HIDE_SYSTEM = Menu.FIRST + 3;
+    public static final int MENU_SHOW_SYSTEM = Menu.FIRST + 2;
+    public static final int MENU_HIDE_SYSTEM = Menu.FIRST + 3;
 
     private ViewGroup mPreferencesContainer;
 
diff --git a/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java b/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java
new file mode 100644
index 0000000..5a0120f
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2020 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 static android.Manifest.permission_group.CAMERA;
+import static android.Manifest.permission_group.LOCATION;
+import static android.Manifest.permission_group.MICROPHONE;
+
+import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_INDICATORS_INTERACTED;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_DISMISS;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_LINE_ITEM;
+import static com.android.permissioncontroller.permission.debug.UtilsKt.shouldShowPermissionsDashboard;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.text.Html;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Observer;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.permissioncontroller.PermissionControllerStatsLog;
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.data.OpAccess;
+import com.android.permissioncontroller.permission.data.OpUsageLiveData;
+import com.android.permissioncontroller.permission.debug.PermissionUsages;
+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.legacy.PermissionApps;
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
+import com.android.permissioncontroller.permission.utils.Utils;
+
+import java.text.Collator;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A dialog listing the currently uses of camera, microphone, and location.
+ */
+public class ReviewOngoingUsageFragment extends PreferenceFragmentCompat {
+
+    // TODO: Replace with OPSTR... APIs
+    static final String PHONE_CALL = "android:phone_call_microphone";
+    static final String VIDEO_CALL = "android:phone_call_camera";
+
+    private @NonNull PermissionUsages mPermissionUsages;
+    private boolean mPermissionUsagesLoaded;
+    private @Nullable AlertDialog mDialog;
+    private OpUsageLiveData mOpUsageLiveData;
+    private @Nullable Map<String, List<OpAccess>> mOpUsage;
+    private ArraySet<String> mSystemUsage = new ArraySet<>(0);
+    private long mStartTime;
+
+    /**
+     * @return A new {@link ReviewOngoingUsageFragment}
+     */
+    public static ReviewOngoingUsageFragment newInstance(long numMillis) {
+        ReviewOngoingUsageFragment fragment = new ReviewOngoingUsageFragment();
+        Bundle arguments = new Bundle();
+        arguments.putLong(Intent.EXTRA_DURATION_MILLIS, numMillis);
+        fragment.setArguments(arguments);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        long numMillis = getArguments().getLong(Intent.EXTRA_DURATION_MILLIS);
+
+        mPermissionUsages = new PermissionUsages(getActivity());
+        mStartTime = Math.max(System.currentTimeMillis() - numMillis, Instant.EPOCH.toEpochMilli());
+        String[] permissions = new String[]{CAMERA, MICROPHONE};
+        if (shouldShowPermissionsDashboard()) {
+            permissions = new String[] {CAMERA, LOCATION, MICROPHONE};
+        }
+        ArrayList<String> appOps = new ArrayList<>(List.of(PHONE_CALL, VIDEO_CALL));
+        mOpUsageLiveData = OpUsageLiveData.Companion.get(appOps, numMillis);
+        mOpUsageLiveData.observeStale(this, new Observer<Map<String, List<OpAccess>>>() {
+            @Override
+            public void onChanged(Map<String, List<OpAccess>> opUsage) {
+                if (mOpUsageLiveData.isStale()) {
+                    return;
+                }
+                mOpUsage = opUsage;
+                mOpUsageLiveData.removeObserver(this);
+
+                if (mPermissionUsagesLoaded) {
+                    onPermissionUsagesLoaded();
+                }
+            }
+        });
+        mPermissionUsages.load(null, permissions, mStartTime, Long.MAX_VALUE,
+                PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(), false, false,
+                this::onPermissionUsagesLoaded, false);
+    }
+
+    private void onPermissionUsagesLoaded() {
+        mPermissionUsagesLoaded = true;
+        if (getActivity() == null || mOpUsage == null) {
+            return;
+        }
+
+        List<AppPermissionUsage> appPermissionUsages = mPermissionUsages.getUsages();
+
+        List<Pair<AppPermissionUsage, List<GroupUsage>>> usages = new ArrayList<>();
+        ArrayList<PermissionApp> permApps = new ArrayList<>();
+        int numApps = appPermissionUsages.size();
+        for (int appNum = 0; appNum < numApps; appNum++) {
+            AppPermissionUsage appUsage = appPermissionUsages.get(appNum);
+
+            List<GroupUsage> usedGroups = new ArrayList<>();
+            List<GroupUsage> appGroups = appUsage.getGroupUsages();
+            int numGroups = appGroups.size();
+            for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+                GroupUsage groupUsage = appGroups.get(groupNum);
+                String groupName = groupUsage.getGroup().getName();
+
+                if (!groupUsage.isRunning()) {
+                    if (groupUsage.getLastAccessDuration() == -1) {
+                        if (groupUsage.getLastAccessTime() < mStartTime) {
+                            continue;
+                        }
+                    } else {
+                        // TODO: Warning: Only works for groups with a single permission as it is
+                        // not guaranteed the last access time and duration refer to same permission
+                        // in AppPermissionUsage#lastAccessAggregate
+                        if (groupUsage.getLastAccessTime() + groupUsage.getLastAccessDuration()
+                                < mStartTime) {
+                            continue;
+                        }
+                    }
+                }
+
+                if (Utils.isGroupOrBgGroupUserSensitive(groupUsage.getGroup())) {
+                    usedGroups.add(appGroups.get(groupNum));
+                } else if (getContext().getSystemService(LocationManager.class).isProviderPackage(
+                        appUsage.getPackageName())
+                        && (groupName.equals(CAMERA) || groupName.equals(MICROPHONE))) {
+                    mSystemUsage.add(groupName);
+                }
+            }
+
+            if (!usedGroups.isEmpty()) {
+                usages.add(Pair.create(appUsage, usedGroups));
+                permApps.add(appUsage.getApp());
+            }
+        }
+
+        if (usages.isEmpty() && mOpUsage.isEmpty() && mSystemUsage.isEmpty()) {
+            getActivity().finish();
+            return;
+        }
+
+        new PermissionApps.AppDataLoader(getActivity(), () -> showDialog(usages))
+                .execute(permApps.toArray(new PermissionApps.PermissionApp[permApps.size()]));
+    }
+
+    private void showDialog(@NonNull List<Pair<AppPermissionUsage, List<GroupUsage>>> usages) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setView(createDialogView(usages))
+                .setPositiveButton(R.string.ongoing_usage_dialog_ok, (dialog, which) ->
+                        PermissionControllerStatsLog.write(PRIVACY_INDICATORS_INTERACTED,
+                                PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_DISMISS))
+                .setOnDismissListener((dialog) -> getActivity().finish());
+        mDialog = builder.create();
+        mDialog.show();
+    }
+
+    /**
+     * Get a list of permission labels.
+     *
+     * @param groups map<perm group name, perm group label>
+     *
+     * @return A localized string with the list of permissions
+     */
+    private CharSequence getListOfPermissionLabels(ArrayMap<String, CharSequence> groups) {
+        int numGroups = groups.size();
+
+        if (numGroups == 1) {
+            return groups.valueAt(0);
+        } else if (numGroups == 2 && groups.containsKey(MICROPHONE) && groups.containsKey(CAMERA)) {
+            // Special case camera + mic permission to be localization friendly
+            return getContext().getString(R.string.permgroup_list_microphone_and_camera);
+        } else {
+            // TODO: Use internationalization safe concatenation
+
+            ArrayList<CharSequence> sortedGroups = new ArrayList<>(groups.values());
+            Collator collator = Collator.getInstance(
+                    getResources().getConfiguration().getLocales().get(0));
+            sortedGroups.sort(collator);
+
+            StringBuilder listBuilder = new StringBuilder();
+
+            for (int i = 0; i < numGroups; i++) {
+                listBuilder.append(sortedGroups.get(i));
+                if (i < numGroups - 2) {
+                    listBuilder.append(getString(R.string.ongoing_usage_dialog_separator));
+                } else if (i < numGroups - 1) {
+                    listBuilder.append(getString(R.string.ongoing_usage_dialog_last_separator));
+                }
+            }
+
+            return listBuilder;
+        }
+    }
+
+    private @NonNull View createDialogView(
+            @NonNull List<Pair<AppPermissionUsage, List<GroupUsage>>> usages) {
+        Context context = getActivity();
+        LayoutInflater inflater = LayoutInflater.from(context);
+        View contentView = inflater.inflate(R.layout.ongoing_usage_dialog_content, null);
+        ViewGroup appsList = contentView.requireViewById(R.id.items_container);
+
+        // Compute all of the permission group labels that were used.
+        ArrayMap<String, CharSequence> usedGroups = new ArrayMap<>();
+        int numUsages = usages.size();
+        for (int usageNum = 0; usageNum < numUsages; usageNum++) {
+            List<GroupUsage> groups = usages.get(usageNum).second;
+            int numGroups = groups.size();
+            for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+                AppPermissionGroup group = groups.get(groupNum).getGroup();
+                usedGroups.put(group.getName(), group.getLabel());
+            }
+        }
+
+        TextView otherUseHeader = contentView.requireViewById(R.id.other_use_header);
+        TextView otherUseContent = contentView.requireViewById(R.id.other_use_content);
+        TextView systemUseContent = contentView.requireViewById(R.id.system_use_content);
+        View otherUseSpacer = contentView.requireViewById(R.id.other_use_inside_spacer);
+
+        if (mOpUsage.isEmpty() && mSystemUsage.isEmpty()) {
+            otherUseHeader.setVisibility(View.GONE);
+            otherUseContent.setVisibility(View.GONE);
+        }
+
+        if (numUsages == 0) {
+            otherUseHeader.setVisibility(View.GONE);
+            appsList.setVisibility(View.GONE);
+        }
+
+        if (mOpUsage.isEmpty() || mSystemUsage.isEmpty()) {
+            otherUseSpacer.setVisibility(View.GONE);
+        }
+
+        if (mOpUsage.isEmpty()) {
+            otherUseContent.setVisibility(View.GONE);
+        }
+
+        if (mSystemUsage.isEmpty()) {
+            systemUseContent.setVisibility(View.GONE);
+        }
+
+        if (!mOpUsage.isEmpty()) {
+            if (mOpUsage.containsKey(VIDEO_CALL) && mOpUsage.containsKey(
+                    PHONE_CALL)) {
+                otherUseContent.setText(
+                        Html.fromHtml(getString(R.string.phone_call_uses_microphone_and_camera),
+                                0));
+            } else if (mOpUsage.containsKey(VIDEO_CALL)) {
+                otherUseContent.setText(
+                        Html.fromHtml(getString(R.string.phone_call_uses_camera), 0));
+            } else if (mOpUsage.containsKey(PHONE_CALL)) {
+                otherUseContent.setText(
+                        Html.fromHtml(getString(R.string.phone_call_uses_microphone), 0));
+            }
+
+            if (mOpUsage.containsKey(VIDEO_CALL)) {
+                usedGroups.put(CAMERA, KotlinUtils.INSTANCE.getPermGroupLabel(context, CAMERA));
+            }
+
+            if (mOpUsage.containsKey(PHONE_CALL)) {
+                usedGroups.put(MICROPHONE,
+                        KotlinUtils.INSTANCE.getPermGroupLabel(context, MICROPHONE));
+            }
+        }
+
+        if (!mSystemUsage.isEmpty()) {
+            if (mSystemUsage.contains(MICROPHONE) && mSystemUsage.contains(CAMERA)) {
+                systemUseContent.setText(getString(R.string.system_uses_microphone_and_camera));
+            } else if (mSystemUsage.contains(CAMERA)) {
+                systemUseContent.setText(getString(R.string.system_uses_camera));
+            } else if (mSystemUsage.contains(MICROPHONE) ) {
+                systemUseContent.setText(getString(R.string.system_uses_microphone));
+            }
+
+            for (String usage : mSystemUsage) {
+                usedGroups.put(usage, KotlinUtils.INSTANCE.getPermGroupLabel(context, usage));
+            }
+        }
+
+        // Add the layout for each app.
+        for (int usageNum = 0; usageNum < numUsages; usageNum++) {
+            Pair<AppPermissionUsage, List<GroupUsage>> usage = usages.get(usageNum);
+            PermissionApp app = usage.first.getApp();
+            List<GroupUsage> groups = usage.second;
+
+            View itemView = inflater.inflate(R.layout.ongoing_usage_dialog_item, appsList, false);
+
+            ((TextView) itemView.requireViewById(R.id.app_name)).setText(app.getLabel());
+            ((ImageView) itemView.requireViewById(R.id.app_icon)).setImageDrawable(app.getIcon());
+
+            // Add the icons for the groups this app used as long as multiple groups were used by
+            // some app.
+            if (usedGroups.size() > 1) {
+                ArrayMap<String, CharSequence> usedGroupsThisApp = new ArrayMap<>();
+
+                ViewGroup iconFrame = itemView.requireViewById(R.id.icons);
+                int numGroups = usages.get(usageNum).second.size();
+                for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+                    AppPermissionGroup group = groups.get(groupNum).getGroup();
+
+                    ViewGroup groupView = (ViewGroup) inflater.inflate(R.layout.image_view, null);
+                    ((ImageView) groupView.requireViewById(R.id.icon)).setImageDrawable(
+                            Utils.applyTint(context, group.getIconResId(),
+                                    android.R.attr.colorControlNormal));
+                    iconFrame.addView(groupView);
+
+                    usedGroupsThisApp.put(group.getName(), group.getLabel());
+                }
+                iconFrame.setVisibility(View.VISIBLE);
+
+                TextView permissionsList = itemView.requireViewById(R.id.permissionsList);
+                permissionsList.setText(getListOfPermissionLabels(usedGroupsThisApp));
+                permissionsList.setVisibility(View.VISIBLE);
+            }
+
+            itemView.setOnClickListener((v) -> {
+                String packageName = app.getPackageName();
+                PermissionControllerStatsLog.write(PRIVACY_INDICATORS_INTERACTED,
+                        PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_LINE_ITEM);
+                UserHandle user = UserHandle.getUserHandleForUid(app.getUid());
+                Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+                intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+                intent.putExtra(Intent.EXTRA_USER, user);
+                context.startActivity(intent);
+                mDialog.dismiss();
+            });
+
+            appsList.addView(itemView);
+        }
+
+        ((TextView) contentView.requireViewById(R.id.title)).setText(
+                getString(R.string.ongoing_usage_dialog_title,
+                        getListOfPermissionLabels(usedGroups)));
+
+        return contentView;
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle bundle, String s) {
+        // empty
+    }
+}
diff --git a/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt b/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
index d2a316a..002c7cc 100644
--- a/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
+++ b/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
@@ -21,6 +21,8 @@
 import android.app.AppOpsManager.MODE_IGNORED
 import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
 import android.Manifest
+import android.app.role.RoleManager
+import android.content.Context.MODE_PRIVATE
 import android.os.Bundle
 import android.os.UserHandle
 import android.util.Log
@@ -28,6 +30,8 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.navigation.fragment.findNavController
+import com.android.permissioncontroller.Constants.ASSISTANT_RECORD_AUDIO_IS_USER_SENSITIVE_KEY
+import com.android.permissioncontroller.Constants.PREFERENCES_FILE
 import com.android.permissioncontroller.PermissionControllerApplication
 import com.android.permissioncontroller.PermissionControllerStatsLog
 import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION
@@ -67,6 +71,8 @@
         val LOG_TAG: String = AppPermissionGroupsViewModel::class.java.simpleName
     }
 
+    val app = PermissionControllerApplication.get()!!
+
     enum class PermSubtitle(val value: Int) {
         NONE(0),
         MEDIA_ONLY(1),
@@ -176,8 +182,7 @@
 
     fun setAutoRevoke(enabled: Boolean) {
         GlobalScope.launch(IPC) {
-            val aom = PermissionControllerApplication.get()
-                .getSystemService(AppOpsManager::class.java)!!
+            val aom = app.getSystemService(AppOpsManager::class.java)!!
             val uid = LightPackageInfoLiveData[packageName, user].getInitializedValue()?.uid
 
             if (uid != null) {
diff --git a/src/com/android/permissioncontroller/permission/utils/UserSensitiveFlagsUtils.kt b/src/com/android/permissioncontroller/permission/utils/UserSensitiveFlagsUtils.kt
index 01f26f4..3cd9891 100644
--- a/src/com/android/permissioncontroller/permission/utils/UserSensitiveFlagsUtils.kt
+++ b/src/com/android/permissioncontroller/permission/utils/UserSensitiveFlagsUtils.kt
@@ -56,14 +56,15 @@
 private fun updateUserSensitiveForUidsInternal(
     uidsUserSensitivity: Map<Int, UidSensitivityState>,
     user: UserHandle,
-    callback: Runnable
+    callback: Runnable?
 ) {
-    val pm = Utils.getUserContext(PermissionControllerApplication.get(), user).packageManager
+    val userContext = Utils.getUserContext(PermissionControllerApplication.get(), user)
+    val pm = userContext.packageManager
 
-        for ((uid, uidState) in uidsUserSensitivity) {
+    for ((uid, uidState) in uidsUserSensitivity) {
             for (pkg in uidState.packages) {
                 for (perm in pkg.requestedPermissions) {
-                    val flags = uidState.permStates[perm] ?: continue
+                    var flags = uidState.permStates[perm] ?: continue
 
                     try {
                         val oldFlags = pm.getPermissionFlags(perm, pkg.packageName, user) and
@@ -83,7 +84,7 @@
                 }
             }
         }
-    callback.run()
+    callback?.run()
 }
 
 /**
@@ -92,7 +93,8 @@
  * @param uid The uid to be updated
  * @param callback A callback which will be executed when finished
  */
-fun updateUserSensitiveForUid(uid: Int, callback: Runnable) {
+@JvmOverloads
+fun updateUserSensitiveForUid(uid: Int, callback: Runnable? = null) {
     GlobalScope.launch(IPC) {
         val uidSensitivityState = UserSensitivityLiveData[uid].getInitializedValue()
         if (uidSensitivityState != null) {
@@ -100,7 +102,7 @@
                 UserHandle.getUserHandleForUid(uid), callback)
         } else {
             Log.e(LOG_TAG, "No packages associated with uid $uid, not updating flags")
-            callback.run()
+            callback?.run()
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java b/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java
index cb5c301..975ed8e 100644
--- a/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java
+++ b/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java
@@ -171,6 +171,14 @@
         return hasAssistantActivity;
     }
 
+    @Override
+    public void grant(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
+    }
+
+    @Override
+    public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
+    }
+
     private boolean isAssistantVoiceInteractionService(@NonNull PackageManager pm,
             @NonNull ServiceInfo si) {
         if (!android.Manifest.permission.BIND_VOICE_INTERACTION.equals(si.permission)) {
diff --git a/tests/inprocess/AndroidTest.xml b/tests/inprocess/AndroidTest.xml
index 656b060..6f3b4c4 100644
--- a/tests/inprocess/AndroidTest.xml
+++ b/tests/inprocess/AndroidTest.xml
@@ -39,6 +39,8 @@
                 value="/data/local/tmp/permissioncontroller/tests/inprocess/AppThatRequestsLocation.apk" />
         <option name="push-file" key="AppThatUsesStoragePermission.apk"
                 value="/data/local/tmp/permissioncontroller/tests/inprocess/AppThatUsesStoragePermission.apk" />
+        <option name="push-file" key="AppThatUsesCameraPermission.apk"
+                value="/data/local/tmp/permissioncontroller/tests/inprocess/AppThatUsesCameraPermission.apk" />
         <option name="push-file" key="AppThatDefinesAdditionalPermission.apk"
                 value="/data/local/tmp/permissioncontroller/tests/inprocess/AppThatDefinesAdditionalPermission.apk" />
         <option name="push-file" key="AppThatUsesAdditionalPermission.apk"
diff --git a/tests/inprocess/AppThatUsesCameraPermission/Android.bp b/tests/inprocess/AppThatUsesCameraPermission/Android.bp
new file mode 100644
index 0000000..285c4f5
--- /dev/null
+++ b/tests/inprocess/AppThatUsesCameraPermission/Android.bp
@@ -0,0 +1,25 @@
+//
+// Copyright (C) 2020 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.
+//
+
+android_test_helper_app {
+    name: "AppThatUsesCameraPermission",
+
+    srcs: ["src/**/*.kt"],
+
+    sdk_version: "current",
+
+    test_suites: [ "device-tests" ],
+}
diff --git a/tests/inprocess/AppThatUsesCameraPermission/AndroidManifest.xml b/tests/inprocess/AppThatUsesCameraPermission/AndroidManifest.xml
new file mode 100644
index 0000000..557c34e
--- /dev/null
+++ b/tests/inprocess/AppThatUsesCameraPermission/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.permissioncontroller.tests.appthatrequestpermission">
+    <uses-permission android:name="android.permission.CAMERA"/>
+
+    <application android:label="CameraRequestApp">
+        <activity android:name=".DummyActivity"
+                  android:exported="true"/>
+    </application>
+</manifest>
+
diff --git a/tests/inprocess/AppThatUsesCameraPermission/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt b/tests/inprocess/AppThatUsesCameraPermission/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt
new file mode 100644
index 0000000..2a4900c
--- /dev/null
+++ b/tests/inprocess/AppThatUsesCameraPermission/src/com/android/permissioncontroller/tests/appthatrequestpermission/DummyActivity.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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.tests.appthatrequestpermission
+
+import android.app.Activity
+
+class DummyActivity : Activity() {
+}
\ No newline at end of file
diff --git a/tests/inprocess/src/com/android/permissioncontroller/permission/PermissionHub2Test.kt b/tests/inprocess/src/com/android/permissioncontroller/permission/PermissionHub2Test.kt
new file mode 100644
index 0000000..da43d20
--- /dev/null
+++ b/tests/inprocess/src/com/android/permissioncontroller/permission/PermissionHub2Test.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.OPSTR_CAMERA
+import android.content.ComponentName
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_PRIVACY
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.google.common.truth.Truth.assertThat
+import org.junit.AfterClass
+import org.junit.BeforeClass
+
+/**
+ * Super class with utilities for testing permission hub 2 code
+ */
+open class PermissionHub2Test {
+    private val APP = "com.android.permissioncontroller.tests.appthatrequestpermission"
+
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context = instrumentation.targetContext
+
+    companion object {
+        private const val PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"
+
+        private var wasPermissionHubEnabled = false
+
+        @JvmStatic
+        @BeforeClass
+        fun enablePermissionHub2() {
+            wasPermissionHubEnabled = DeviceConfig.getBoolean(NAMESPACE_PRIVACY,
+                    PROPERTY_PERMISSIONS_HUB_2_ENABLED, false)
+
+            if (!wasPermissionHubEnabled) {
+                runShellCommand(
+                        "device_config put privacy $PROPERTY_PERMISSIONS_HUB_2_ENABLED true")
+            }
+        }
+
+        @JvmStatic
+        @AfterClass
+        fun disablePermissionHub2() {
+            if (!wasPermissionHubEnabled) {
+                runShellCommand(
+                        "device_config put privacy $PROPERTY_PERMISSIONS_HUB_2_ENABLED false")
+            }
+        }
+    }
+
+    /**
+     * Make {@value #APP} access the camera
+     */
+    protected fun accessCamera() {
+        // App needs to be in foreground to be able to access camera
+        context.startActivity(
+                Intent().setComponent(ComponentName.createRelative(APP, ".DummyActivity"))
+                        .setFlags(FLAG_ACTIVITY_NEW_TASK))
+
+        eventually {
+            assertThat(
+                    SystemUtil.callWithShellPermissionIdentity {
+                        context.getSystemService(AppOpsManager::class.java).startOp(
+                                OPSTR_CAMERA, context.packageManager.getPackageUid(APP, 0),
+                                APP, null, null)
+                    }).isEqualTo(MODE_ALLOWED)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/inprocess/src/com/android/permissioncontroller/permission/debug/PermissionUsageFragmentTest.kt b/tests/inprocess/src/com/android/permissioncontroller/permission/debug/PermissionUsageFragmentTest.kt
new file mode 100644
index 0000000..ecffcb2
--- /dev/null
+++ b/tests/inprocess/src/com/android/permissioncontroller/permission/debug/PermissionUsageFragmentTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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 android.Manifest.permission.CAMERA
+import android.content.Intent
+import android.permission.cts.PermissionUtils.grantPermission
+import android.permission.cts.PermissionUtils.install
+import android.permission.cts.PermissionUtils.uninstallApp
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.rule.ActivityTestRule
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.getPreferenceSummary
+import com.android.permissioncontroller.permission.PermissionHub2Test
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
+import com.android.permissioncontroller.scrollToPreference
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Simple tests for {@link PermissionUsageFragment}
+ */
+@RunWith(AndroidJUnit4::class)
+class PermissionUsageFragmentTest : PermissionHub2Test() {
+    private val APK =
+            "/data/local/tmp/permissioncontroller/tests/inprocess/AppThatUsesCameraPermission.apk"
+    private val APP = "com.android.permissioncontroller.tests.appthatrequestpermission"
+    private val APP_LABEL = "CameraRequestApp"
+
+    @get:Rule
+    val managePermissionsActivity = object : ActivityTestRule<ManagePermissionsActivity>(
+            ManagePermissionsActivity::class.java) {
+        override fun getActivityIntent() = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
+
+        override fun beforeActivityLaunched() {
+            install(APK)
+            grantPermission(APP, CAMERA)
+
+            accessCamera()
+        }
+    }
+
+    @Test
+    fun cameraAccessShouldBeShown() {
+        eventually {
+            try {
+                scrollToPreference(APP_LABEL)
+            } catch (e: Exception) {
+                onView(withContentDescription(R.string.permission_usage_refresh)).perform(click())
+                throw e
+            }
+        }
+
+        assertThat(getPreferenceSummary(APP_LABEL)).isEqualTo("Camera")
+
+        // Expand usage
+        onView(withText(APP_LABEL)).perform(click())
+    }
+
+    @After
+    fun uninstallTestApp() {
+        uninstallApp(APP)
+    }
+}
\ No newline at end of file
diff --git a/tests/inprocess/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragmentTest.kt b/tests/inprocess/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragmentTest.kt
new file mode 100644
index 0000000..bd0d1f2
--- /dev/null
+++ b/tests/inprocess/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragmentTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 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.Manifest.permission.CAMERA
+import android.permission.cts.PermissionUtils.grantPermission
+import android.permission.cts.PermissionUtils.install
+import android.permission.cts.PermissionUtils.uninstallApp
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.matcher.RootMatchers.isDialog
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.rule.ActivityTestRule
+import com.android.permissioncontroller.permission.PermissionHub2Test
+import com.android.permissioncontroller.permission.ui.ReviewOngoingUsageActivity
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Simple tests for {@link ReviewOngoingUsageFragment}
+ */
+class ReviewOngoingUsageFragmentTest : PermissionHub2Test() {
+    private val APK =
+            "/data/local/tmp/permissioncontroller/tests/inprocess/AppThatUsesCameraPermission.apk"
+    private val APP = "com.android.permissioncontroller.tests.appthatrequestpermission"
+    private val APP_LABEL = "CameraRequestApp"
+
+    @get:Rule
+    val managePermissionsActivity = object : ActivityTestRule<ReviewOngoingUsageActivity>(
+            ReviewOngoingUsageActivity::class.java) {
+        override fun beforeActivityLaunched() {
+            install(APK)
+            grantPermission(APP, CAMERA)
+
+            accessCamera()
+        }
+    }
+
+    @Test
+    fun cameraAccessShouldBeShown() {
+        // Click on app entry
+        onView(withText(APP_LABEL))
+                .inRoot(isDialog())
+                .perform(click())
+    }
+
+    @After
+    fun uninstallTestApp() {
+        uninstallApp(APP)
+    }
+}
\ No newline at end of file
diff --git a/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt b/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt
index 0d8a6fb..4963a46 100644
--- a/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt
+++ b/tests/outofprocess/src/com/android/permissioncontroller/tests/outofprocess/DumpTest.kt
@@ -24,6 +24,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.protobuf.InvalidProtocolBufferException
 import org.junit.Assert.fail
+import org.junit.Assume.assumeTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.nio.charset.StandardCharsets.UTF_8
@@ -49,12 +50,22 @@
 
     @Test
     fun autoRevokeDumpHasCurrentUser() {
-        assertThat(getDump().autoRevoke.usersList.map { it.userId }).contains(myUserId())
+        val dump = getDump()
+
+        // Sometimes the dump takes to long to get generated, esp. on low end devices
+        assumeTrue(dump.autoRevoke.usersList.isNotEmpty())
+
+        assertThat(dump.autoRevoke.usersList.map { it.userId }).contains(myUserId())
     }
 
     @Test
     fun autoRevokeDumpHasAndroidPackage() {
-        assertThat(getDump().autoRevoke.usersList[myUserId()].packagesList.map { it.packageName })
+        val dump = getDump()
+
+        // Sometimes the dump takes to long to get generated, esp. on low end devices
+        assumeTrue(dump.autoRevoke.usersList.isNotEmpty())
+
+        assertThat(dump.autoRevoke.usersList[myUserId()].packagesList.map { it.packageName })
                 .contains(OS_PKG)
     }
 }