diff --git a/res/values/strings.xml b/res/values/strings.xml
index 723f7aa06b3..9327332a227 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -10512,4 +10512,14 @@
Silence alerts gesture
+
+
+ 0 apps used permissions
+
+
+ Most-used permissions in last 24 hours
+
+
+ See all usage
+
diff --git a/res/xml/privacy_dashboard_settings.xml b/res/xml/privacy_dashboard_settings.xml
index a580f5ff91e..44ac10a3204 100644
--- a/res/xml/privacy_dashboard_settings.xml
+++ b/res/xml/privacy_dashboard_settings.xml
@@ -22,10 +22,16 @@
android:title="@string/privacy_dashboard_title"
settings:initialExpandedChildrenCount="3">
+
+
diff --git a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java
new file mode 100644
index 00000000000..3fac672b608
--- /dev/null
+++ b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privacy;
+
+import static com.android.settingslib.widget.BarChartPreference.MAXIMUM_BAR_VIEWS;
+
+import static java.util.concurrent.TimeUnit.DAYS;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.permission.PermissionControllerManager;
+import android.permission.RuntimePermissionUsageInfo;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.BarChartInfo;
+import com.android.settingslib.widget.BarChartPreference;
+import com.android.settingslib.widget.BarViewInfo;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Executors;
+
+
+public class PermissionBarChartPreferenceController extends BasePreferenceController implements
+ PermissionControllerManager.OnPermissionUsageResultCallback {
+
+ private static final String TAG = "BarChartPreferenceCtl";
+
+ private PackageManager mPackageManager;
+ private BarChartPreference mBarChartPreference;
+ private List mOldUsageInfos;
+
+ public PermissionBarChartPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mOldUsageInfos = new ArrayList<>();
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mBarChartPreference = (BarChartPreference) screen.findPreference(getPreferenceKey());
+
+ final BarChartInfo info = new BarChartInfo.Builder()
+ .setTitle(R.string.permission_bar_chart_title)
+ .setDetails(R.string.permission_bar_chart_details)
+ .setEmptyText(R.string.permission_bar_chart_empty_text)
+ .setDetailsOnClickListener((View v) -> {
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE);
+ mContext.startActivity(intent);
+ })
+ .build();
+
+ mBarChartPreference.initializeBarChart(info);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ retrievePermissionUsageData();
+ }
+
+ @Override
+ public void onPermissionUsageResult(@NonNull List usageInfos) {
+ usageInfos.sort(Comparator.comparingInt(
+ RuntimePermissionUsageInfo::getAppAccessCount).reversed());
+
+ // If the result is different, we need to update bar views.
+ if (!areSamePermissionGroups(usageInfos)) {
+ mBarChartPreference.setBarViewInfos(createBarViews(usageInfos));
+ mOldUsageInfos = usageInfos;
+ }
+ }
+
+ private void retrievePermissionUsageData() {
+ mContext.getSystemService(PermissionControllerManager.class).getPermissionUsages(
+ false /* countSystem */, (int) DAYS.toSeconds(1),
+ mContext.getMainExecutor() /* executor */, this /* callback */);
+ }
+
+ private BarViewInfo[] createBarViews(List usageInfos) {
+ if (usageInfos.isEmpty()) {
+ return null;
+ }
+
+ final BarViewInfo[] barViewInfos = new BarViewInfo[
+ Math.min(BarChartPreference.MAXIMUM_BAR_VIEWS, usageInfos.size())];
+
+ for (int index = 0; index < barViewInfos.length; index++) {
+ final RuntimePermissionUsageInfo permissionGroupInfo = usageInfos.get(index);
+
+ barViewInfos[index] = new BarViewInfo(
+ getPermissionGroupIcon(permissionGroupInfo.getName()),
+ permissionGroupInfo.getAppAccessCount(),
+ R.string.storage_detail_apps);
+
+ // Set the click listener for each bar view.
+ // The listener will navigate user to permission usage app.
+ barViewInfos[index].setClickListener((View v) -> {
+ final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE);
+ intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permissionGroupInfo.getName());
+ mContext.startActivity(intent);
+ });
+ }
+
+ return barViewInfos;
+ }
+
+ private Drawable getPermissionGroupIcon(CharSequence permissionGroup) {
+ Drawable icon = null;
+ try {
+ icon = mPackageManager.getPermissionGroupInfo(permissionGroup.toString(), 0)
+ .loadIcon(mPackageManager);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Cannot find group icon for " + permissionGroup, e);
+ }
+
+ return icon;
+ }
+
+ private boolean areSamePermissionGroups(List newUsageInfos) {
+ if (newUsageInfos.size() != mOldUsageInfos.size()) {
+ return false;
+ }
+
+ for (int index = 0; index < newUsageInfos.size(); index++) {
+ final RuntimePermissionUsageInfo newInfo = newUsageInfos.get(index);
+ final RuntimePermissionUsageInfo oldInfo = mOldUsageInfos.get(index);
+
+ if (!newInfo.getName().equals(oldInfo.getName()) ||
+ newInfo.getAppAccessCount() != oldInfo.getAppAccessCount()) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java
new file mode 100644
index 00000000000..119ac7db599
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privacy;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.permission.RuntimePermissionUsageInfo;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settingslib.widget.BarChartInfo;
+import com.android.settingslib.widget.BarChartPreference;
+import com.android.settingslib.widget.BarViewInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class PermissionBarChartPreferenceControllerTest {
+
+ @Mock
+ private PreferenceScreen mScreen;
+
+ private PermissionBarChartPreferenceController mController;
+ private BarChartPreference mPreference;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context Context = RuntimeEnvironment.application;
+ mController = new PermissionBarChartPreferenceController(Context, "test_key");
+ mPreference = spy(new BarChartPreference(Context));
+ when(mScreen.findPreference(mController.getPreferenceKey()))
+ .thenReturn((BarChartPreference) mPreference);
+ }
+
+ @Test
+ public void getAvailabilityStatus_shouldReturnAvailableUnsearchable() {
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
+ }
+
+ @Test
+ public void displayPreference_shouldInitializeBarChart() {
+ mController.displayPreference(mScreen);
+
+ verify(mPreference).initializeBarChart(any(BarChartInfo.class));
+ }
+
+ @Test
+ public void onPermissionUsageResult_differentPermissionResultSet_shouldSetBarViewInfos() {
+ final List infos1 = new ArrayList<>();
+ final RuntimePermissionUsageInfo info1 =
+ new RuntimePermissionUsageInfo("permission 1", 10);
+ infos1.add(info1);
+ mController.displayPreference(mScreen);
+ mController.onPermissionUsageResult(infos1);
+
+ verify(mPreference).setBarViewInfos(any(BarViewInfo[].class));
+
+ final List infos2 = new ArrayList<>();
+ final RuntimePermissionUsageInfo info2 =
+ new RuntimePermissionUsageInfo("permission 2", 20);
+ infos2.add(info2);
+ mController.onPermissionUsageResult(infos2);
+
+ verify(mPreference, times(2)).setBarViewInfos(any(BarViewInfo[].class));
+ }
+
+ @Test
+ public void onPermissionUsageResult_samePermissionResultSet_shouldNotSetBarViewInfos() {
+ final List mInfos = new ArrayList<>();
+ final RuntimePermissionUsageInfo info1 =
+ new RuntimePermissionUsageInfo("permission 1", 10);
+ mInfos.add(info1);
+ mController.displayPreference(mScreen);
+ mController.onPermissionUsageResult(mInfos);
+
+ mController.onPermissionUsageResult(mInfos);
+
+ verify(mPreference, times(1)).setBarViewInfos(any(BarViewInfo[].class));
+ }
+}