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)); + } +}