Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9423d764 authored by Michael Bestas's avatar Michael Bestas Committed by Michael Bestas
Browse files

Revert "Remove permission bar chart in Privacy setting"

This reverts commit ab914adc.

Change-Id: I9656fd074ad312144b5f9019930ea30643422fab
parent fc6c9a5a
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -22,6 +22,12 @@
    android:title="@string/privacy_dashboard_title"
    settings:initialExpandedChildrenCount="10">

    <!-- This preference isn't searchable, and user won't see title in this preference.
         So, we just set empty text for title. -->
    <com.android.settingslib.widget.BarChartPreference
        android:key="permission_bar_chart"
        android:title="@string/summary_placeholder"
        settings:controller="com.android.settings.privacy.PermissionBarChartPreferenceController"/>

    <!-- Work Policy info -->
    <Preference
+249 −0
Original line number Diff line number Diff line
/*
 * 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 android.Manifest.permission_group.CAMERA;
import static android.Manifest.permission_group.LOCATION;
import static android.Manifest.permission_group.MICROPHONE;

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.os.Bundle;
import android.permission.PermissionControllerManager;
import android.permission.RuntimePermissionUsageInfo;
import android.provider.DeviceConfig;
import android.util.Log;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.Utils;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreate;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
import com.android.settingslib.core.lifecycle.events.OnStart;
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.List;


public class PermissionBarChartPreferenceController extends BasePreferenceController implements
        PermissionControllerManager.OnPermissionUsageResultCallback, LifecycleObserver, OnCreate,
        OnStart, OnSaveInstanceState {

    private static final String TAG = "BarChartPreferenceCtl";
    private static final String KEY_PERMISSION_USAGE = "usage_infos";

    @VisibleForTesting
    List<RuntimePermissionUsageInfo> mOldUsageInfos;
    private PackageManager mPackageManager;
    private PrivacyDashboardFragment mParent;
    private BarChartPreference mBarChartPreference;

    public PermissionBarChartPreferenceController(Context context, String preferenceKey) {
        super(context, preferenceKey);
        mOldUsageInfos = new ArrayList<>();
        mPackageManager = context.getPackageManager();
    }

    public void setFragment(PrivacyDashboardFragment fragment) {
        mParent = fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            mOldUsageInfos = savedInstanceState.getParcelableArrayList(KEY_PERMISSION_USAGE);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putParcelableList(KEY_PERMISSION_USAGE, mOldUsageInfos);
    }

    @Override
    public int getAvailabilityStatus() {
        return Boolean.parseBoolean(
                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PRIVACY,
                        com.android.settings.Utils.PROPERTY_PERMISSIONS_HUB_ENABLED)) ?
                AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mBarChartPreference = 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);
                    intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1));
                    mContext.startActivity(intent);
                })
                .build();

        mBarChartPreference.initializeBarChart(info);
        if (!mOldUsageInfos.isEmpty()) {
            mBarChartPreference.setBarViewInfos(createBarViews(mOldUsageInfos));
        }
    }

    @Override
    public void onStart() {
        if (!isAvailable()) {
            return;
        }

        // Add a shadow animation to action bar scroll only when the chart is available.
        com.android.settings.Utils.setActionBarShadowAnimation(mParent.getActivity(),
                mParent.getSettingsLifecycle(), mParent.getListView());
        // We don't hide chart when we have existing data.
        mBarChartPreference.updateLoadingState(mOldUsageInfos.isEmpty() /* isLoading */);
        // But we still need to hint user with progress bar that we are updating new usage data.
        mParent.showPinnedHeader(true);
        retrievePermissionUsageData();
    }

    @Override
    public void onPermissionUsageResult(@NonNull List<RuntimePermissionUsageInfo> usageInfos) {
        usageInfos.sort((x, y) -> {
            int usageDiff = y.getAppAccessCount() - x.getAppAccessCount();
            if (usageDiff != 0) {
                return usageDiff;
            }
            String xName = x.getName();
            String yName = y.getName();
            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());
        });

        // If the result is different, we need to update bar views.
        if (!areSamePermissionGroups(usageInfos)) {
            mBarChartPreference.setBarViewInfos(createBarViews(usageInfos));
            mOldUsageInfos = usageInfos;
        }

        mBarChartPreference.updateLoadingState(false /* isLoading */);
        mParent.showPinnedHeader(false);
    }

    private void retrievePermissionUsageData() {
        mContext.getSystemService(PermissionControllerManager.class).getPermissionUsages(
                false /* countSystem */, (int) DAYS.toMillis(1),
                mContext.getMainExecutor() /* executor */, this /* callback */);
    }

    private BarViewInfo[] createBarViews(List<RuntimePermissionUsageInfo> 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);
            final int count = permissionGroupInfo.getAppAccessCount();
            final CharSequence permLabel = getPermissionGroupLabel(permissionGroupInfo.getName());

            barViewInfos[index] = new BarViewInfo(
                    getPermissionGroupIcon(permissionGroupInfo.getName()), count, permLabel,
                    mContext.getResources().getQuantityString(R.plurals.permission_bar_chart_label,
                            count, count), permLabel);

            // 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());
                intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1));
                mContext.startActivity(intent);
            });
        }

        return barViewInfos;
    }

    private Drawable getPermissionGroupIcon(String permissionGroup) {
        Drawable icon = null;
        try {
            icon = mPackageManager.getPermissionGroupInfo(permissionGroup, 0)
                    .loadIcon(mPackageManager);
            icon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorSecondary));
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Cannot find group icon for " + permissionGroup, e);
        }

        return icon;
    }

    private CharSequence getPermissionGroupLabel(String permissionGroup) {
        CharSequence label = null;
        try {
            label = mPackageManager.getPermissionGroupInfo(permissionGroup, 0)
                    .loadLabel(mPackageManager);
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Cannot find group label for " + permissionGroup, e);
        }

        return label;
    }

    private boolean areSamePermissionGroups(List<RuntimePermissionUsageInfo> 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;
    }
}
+22 −0
Original line number Diff line number Diff line
@@ -18,6 +18,10 @@ package com.android.settings.privacy;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.view.View;

import androidx.annotation.VisibleForTesting;

import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
@@ -64,6 +68,24 @@ public class PrivacyDashboardFragment extends DashboardFragment {
        return buildPreferenceControllers(context, getSettingsLifecycle());
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        use(PermissionBarChartPreferenceController.class).setFragment(this /* fragment */);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initLoadingBar();
    }

    @VisibleForTesting
    void initLoadingBar() {
        setPinnedHeaderView(R.layout.progress_header);
        showPinnedHeader(false);
    }

    private static List<AbstractPreferenceController> buildPreferenceControllers(
            Context context, Lifecycle lifecycle) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
+262 −0
Original line number Diff line number Diff line
/*
 * 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 android.Manifest.permission_group.CALENDAR;
import static android.Manifest.permission_group.CAMERA;
import static android.Manifest.permission_group.CONTACTS;
import static android.Manifest.permission_group.LOCATION;
import static android.Manifest.permission_group.MICROPHONE;
import static android.Manifest.permission_group.PHONE;
import static android.Manifest.permission_group.SMS;

import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
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.content.pm.UserInfo;
import android.os.UserManager;
import android.permission.RuntimePermissionUsageInfo;
import android.provider.DeviceConfig;
import android.view.accessibility.AccessibilityManager;

import androidx.preference.PreferenceScreen;

import com.android.internal.widget.LockPatternUtils;
import com.android.settings.Utils;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settings.testutils.shadow.ShadowPermissionControllerManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settingslib.widget.BarChartInfo;
import com.android.settingslib.widget.BarChartPreference;
import com.android.settingslib.widget.BarViewInfo;

import org.junit.After;
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 org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowAccessibilityManager;
import org.robolectric.shadows.androidx.fragment.FragmentController;

import java.util.ArrayList;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowDeviceConfig.class, ShadowUserManager.class,
        ShadowPermissionControllerManager.class})
public class PermissionBarChartPreferenceControllerTest {

    @Mock
    private PreferenceScreen mScreen;
    @Mock
    private LockPatternUtils mLockPatternUtils;

    private PermissionBarChartPreferenceController mController;
    private BarChartPreference mPreference;
    private PrivacyDashboardFragment mFragment;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        final Context context = RuntimeEnvironment.application;
        final UserManager userManager = context.getSystemService(UserManager.class);
        final ShadowUserManager shadowUserManager = Shadow.extract(userManager);
        final ShadowAccessibilityManager accessibilityManager = Shadow.extract(
                AccessibilityManager.getInstance(context));
        accessibilityManager.setEnabledAccessibilityServiceList(new ArrayList<>());
        shadowUserManager.addProfile(new UserInfo(123, null, 0));
        when(FakeFeatureFactory.setupForTest().securityFeatureProvider.getLockPatternUtils(
                any(Context.class))).thenReturn(mLockPatternUtils);

        mController = spy(new PermissionBarChartPreferenceController(context, "test_key"));
        mFragment = spy(FragmentController.of(new PrivacyDashboardFragment())
                .create().start().get());
        mController.setFragment(mFragment);
        mPreference = spy(new BarChartPreference(context));
        when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
    }

    @After
    public void tearDown() {
        ShadowDeviceConfig.reset();
    }

    @Test
    public void getAvailabilityStatus_permissionHubNotSet_shouldReturnUnsupported() {
        // We have not yet set the property to show the Permissions Hub.
        assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
    }

    @Test
    public void getAvailabilityStatus_permissionHubEnabled_shouldReturnAvailableUnsearchable() {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
                Utils.PROPERTY_PERMISSIONS_HUB_ENABLED,
                "true", true);

        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
    }

    @Test
    public void displayPreference_shouldInitializeBarChart() {
        mController.displayPreference(mScreen);

        verify(mPreference).initializeBarChart(any(BarChartInfo.class));
    }

    @Test
    public void displayPreference_usageInfosSet_shouldSetBarViewInfos() {
        final RuntimePermissionUsageInfo info1 =
                new RuntimePermissionUsageInfo("permission 1", 10);
        mController.mOldUsageInfos.add(info1);

        mController.displayPreference(mScreen);

        verify(mPreference).setBarViewInfos(any(BarViewInfo[].class));
        verify(mPreference).initializeBarChart(any(BarChartInfo.class));
    }

    @Test
    public void onPermissionUsageResult_differentPermissionResultSet_shouldSetBarViewInfos() {
        final List<RuntimePermissionUsageInfo> 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<RuntimePermissionUsageInfo> 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<RuntimePermissionUsageInfo> 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));
    }

    @Test
    public void onStart_usageInfosNotSetAndPermissionHubEnabled_shouldShowProgressBar() {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
                Utils.PROPERTY_PERMISSIONS_HUB_ENABLED,
                "true", true);
        mController.displayPreference(mScreen);

        mController.onStart();

        assertThat(mFragment.getActivity().getActionBar().getElevation()).isZero();
        verify(mFragment).showPinnedHeader(true);
        verify(mPreference).updateLoadingState(true /* isLoading */);
    }

    @Test
    public void onStart_usageInfosSetAndPermissionHubEnabled_shouldNotUpdatePrefLoadingState() {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
                Utils.PROPERTY_PERMISSIONS_HUB_ENABLED,
                "true", true);
        final RuntimePermissionUsageInfo info1 =
                new RuntimePermissionUsageInfo("permission 1", 10);
        mController.mOldUsageInfos.add(info1);
        mController.displayPreference(mScreen);

        mController.onStart();

        assertThat(mFragment.getActivity().getActionBar().getElevation()).isZero();
        verify(mFragment).showPinnedHeader(true);
        verify(mPreference).updateLoadingState(false /* isLoading */);
    }

    @Test
    public void onStart_permissionHubDisabled_shouldNotShowProgressBar() {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
                Utils.PROPERTY_PERMISSIONS_HUB_ENABLED,
                "false", true);

        mController.onStart();

        assertThat(mFragment.getActivity().getActionBar().getElevation()).isNonZero();
        verify(mFragment, never()).showPinnedHeader(true);
        verify(mPreference, never()).updateLoadingState(true /* isLoading */);
    }

    @Test
    public void onPermissionUsageResult_shouldHideProgressBar() {
        final List<RuntimePermissionUsageInfo> infos1 = new ArrayList<>();
        final RuntimePermissionUsageInfo info1 =
                new RuntimePermissionUsageInfo("permission 1", 10);
        infos1.add(info1);
        mController.displayPreference(mScreen);

        mController.onPermissionUsageResult(infos1);

        verify(mFragment).showPinnedHeader(false);
        verify(mPreference).updateLoadingState(false /* isLoading */);
    }

    @Test
    public void onPermissionUsageResult_shouldBeSorted() {
        final List<RuntimePermissionUsageInfo> infos = new ArrayList<>();
        infos.add(new RuntimePermissionUsageInfo(PHONE, 10));
        infos.add(new RuntimePermissionUsageInfo(LOCATION, 10));
        infos.add(new RuntimePermissionUsageInfo(CAMERA, 10));
        infos.add(new RuntimePermissionUsageInfo(SMS, 1));
        infos.add(new RuntimePermissionUsageInfo(MICROPHONE, 10));
        infos.add(new RuntimePermissionUsageInfo(CONTACTS, 42));
        infos.add(new RuntimePermissionUsageInfo(CALENDAR, 10));
        mController.displayPreference(mScreen);

        mController.onPermissionUsageResult(infos);

        assertThat(infos.get(0).getName()).isEqualTo(CONTACTS);
        assertThat(infos.get(1).getName()).isEqualTo(LOCATION);
        assertThat(infos.get(2).getName()).isEqualTo(MICROPHONE);
        assertThat(infos.get(3).getName()).isEqualTo(CAMERA);
        assertThat(infos.get(4).getName()).isEqualTo(CALENDAR);
        assertThat(infos.get(5).getName()).isEqualTo(PHONE);
        assertThat(infos.get(6).getName()).isEqualTo(SMS);
    }
}
+83 −0

File added.

Preview size limit exceeded, changes collapsed.