From 3da8f8d31dc78f39cc598cce6b0c9012da1e64af Mon Sep 17 00:00:00 2001 From: Lifu Tang Date: Tue, 11 Dec 2018 13:50:34 -0800 Subject: [PATCH] Display app stats for location permission Bug: 120221631 Test: manually Change-Id: I53f43079807759c50eeb62029bb0d8d1f84e1118 --- res/values/strings.xml | 28 +++++- res/xml/top_level_settings.xml | 7 +- .../AppLocationPermissionPreferenceController.java | 77 ++++++++++++++++- .../android/settings/location/LocationEnabler.java | 16 ++-- .../settings/location/LocationSettings.java | 2 +- .../TopLevelLocationPreferenceController.java | 99 ++++++++++++++++++++++ ...LocationPermissionPreferenceControllerTest.java | 11 ++- .../settings/location/LocationEnablerTest.java | 15 ++-- 8 files changed, 226 insertions(+), 29 deletions(-) create mode 100644 src/com/android/settings/location/TopLevelLocationPreferenceController.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 4abea447d9..be9cc14754 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -827,8 +827,15 @@ Location Use location - - Scanning, location history + + Off + + + On - %1$d app can access location + On - %1$d apps can access location + + + Loading\u2026 Accounts @@ -3630,7 +3637,22 @@ Location for work profile - App-level permissions + App permission + + Location is off + + + + %1$d + of + %2$d + app has unlimited access + + %1$d + of + %2$d + apps have unlimited access + Recent location access diff --git a/res/xml/top_level_settings.xml b/res/xml/top_level_settings.xml index 03e32dcfb2..9f4f90279f 100644 --- a/res/xml/top_level_settings.xml +++ b/res/xml/top_level_settings.xml @@ -93,10 +93,11 @@ + android:fragment="com.android.settings.location.LocationSettings" + settings:controller="com.android.settings.location.TopLevelLocationPreferenceController"/> - \ No newline at end of file + diff --git a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java index f920fdc7dc..5bfc58447e 100644 --- a/src/com/android/settings/location/AppLocationPermissionPreferenceController.java +++ b/src/com/android/settings/location/AppLocationPermissionPreferenceController.java @@ -1,18 +1,38 @@ package com.android.settings.location; +import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; + import android.content.Context; +import android.location.LocationManager; +import android.permission.RuntimePermissionPresenter; import android.provider.Settings; +import androidx.preference.Preference; + +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; public class AppLocationPermissionPreferenceController extends - AbstractPreferenceController implements PreferenceControllerMixin { + LocationBasePreferenceController implements PreferenceControllerMixin { private static final String KEY_APP_LEVEL_PERMISSIONS = "app_level_permissions"; + /** Total number of apps that has location permission. */ + private int mNumTotal = -1; + /** Total number of apps that has background location permission. */ + private int mNumBackground = -1; + private final LocationManager mLocationManager; + private Preference mPreference; - public AppLocationPermissionPreferenceController(Context context) { - super(context); + public AppLocationPermissionPreferenceController(Context context, Lifecycle lifecycle) { + super(context, lifecycle); + mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); } @Override @@ -25,4 +45,53 @@ public class AppLocationPermissionPreferenceController extends return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED, 1) == 1; } + + @Override + public CharSequence getSummary() { + if (mLocationManager.isLocationEnabled()) { + if (mNumTotal == -1 || mNumBackground == -1) { + return mContext.getString(R.string.location_settings_loading_app_permission_stats); + } + return mContext.getResources().getQuantityString( + R.plurals.location_app_permission_summary_location_on, mNumBackground, + mNumBackground, mNumTotal); + } else { + return mContext.getString(R.string.location_app_permission_summary_location_off); + } + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + mPreference = preference; + final AtomicInteger loadingInProgress = new AtomicInteger(2); + refreshSummary(preference); + // Bail out if location has been disabled. + if (!mLocationManager.isLocationEnabled()) { + return; + } + RuntimePermissionPresenter.getInstance(mContext).countPermissionApps( + Arrays.asList(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION), false, false, + (numApps) -> { + mNumTotal = numApps; + if (loadingInProgress.decrementAndGet() == 0) { + refreshSummary(preference); + } + }, null); + + RuntimePermissionPresenter.getInstance(mContext).countPermissionApps( + Collections.singletonList(ACCESS_BACKGROUND_LOCATION), true, false, + (numApps) -> { + mNumBackground = numApps; + if (loadingInProgress.decrementAndGet() == 0) { + refreshSummary(preference); + } + }, null); + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) { + // 'null' is checked inside updateState(), so no need to check here. + updateState(mPreference); + } } diff --git a/src/com/android/settings/location/LocationEnabler.java b/src/com/android/settings/location/LocationEnabler.java index 20c228024f..e1bdf162ef 100644 --- a/src/com/android/settings/location/LocationEnabler.java +++ b/src/com/android/settings/location/LocationEnabler.java @@ -35,15 +35,15 @@ import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnPause; -import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; /** * A class that listens to location settings change and modifies location settings * settings. */ -public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { +public class LocationEnabler implements LifecycleObserver, OnStart, OnStop { private static final String TAG = "LocationEnabler"; @VisibleForTesting @@ -73,7 +73,7 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { } @Override - public void onResume() { + public void onStart() { if (mReceiver == null) { mReceiver = new BroadcastReceiver() { @Override @@ -90,12 +90,8 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause { } @Override - public void onPause() { - try { - mContext.unregisterReceiver(mReceiver); - } catch (RuntimeException e) { - // Ignore exceptions caused by race condition - } + public void onStop() { + mContext.unregisterReceiver(mReceiver); } void refreshLocationMode() { diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index 8a92f4706e..4112340203 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -122,7 +122,7 @@ public class LocationSettings extends DashboardFragment { private static List buildPreferenceControllers( Context context, LocationSettings fragment, Lifecycle lifecycle) { final List controllers = new ArrayList<>(); - controllers.add(new AppLocationPermissionPreferenceController(context)); + controllers.add(new AppLocationPermissionPreferenceController(context, lifecycle)); controllers.add(new LocationForWorkPreferenceController(context, lifecycle)); controllers.add(new RecentLocationAccessPreferenceController(context)); controllers.add(new LocationScanningPreferenceController(context)); diff --git a/src/com/android/settings/location/TopLevelLocationPreferenceController.java b/src/com/android/settings/location/TopLevelLocationPreferenceController.java new file mode 100644 index 0000000000..d0bd9a9284 --- /dev/null +++ b/src/com/android/settings/location/TopLevelLocationPreferenceController.java @@ -0,0 +1,99 @@ +package com.android.settings.location; + +import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.LocationManager; +import android.permission.RuntimePermissionPresenter; + +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import java.util.Arrays; +import java.util.Collections; + +public class TopLevelLocationPreferenceController extends BasePreferenceController implements + LifecycleObserver, OnStart, OnStop { + private static final IntentFilter INTENT_FILTER_LOCATION_MODE_CHANGED = + new IntentFilter(LocationManager.MODE_CHANGED_ACTION); + private final LocationManager mLocationManager; + /** Total number of apps that has location permission. */ + private int mNumTotal = -1; + private BroadcastReceiver mReceiver; + private Preference mPreference; + + public TopLevelLocationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + if (mLocationManager.isLocationEnabled()) { + if (mNumTotal == -1) { + return mContext.getString(R.string.location_settings_loading_app_permission_stats); + } + return mContext.getResources().getQuantityString( + R.plurals.location_settings_summary_location_on, + mNumTotal, mNumTotal); + } else { + return mContext.getString(R.string.location_settings_summary_location_off); + } + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + mPreference = preference; + refreshSummary(preference); + // Bail out if location has been disabled. + if (!mLocationManager.isLocationEnabled()) { + return; + } + RuntimePermissionPresenter.getInstance(mContext).countPermissionApps( + Arrays.asList(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION), false, false, + (numApps) -> { + mNumTotal = numApps; + refreshSummary(preference); + }, null); + } + + @Override + public void onStart() { + if (mReceiver == null) { + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + refreshLocationMode(); + } + }; + } + mContext.registerReceiver(mReceiver, INTENT_FILTER_LOCATION_MODE_CHANGED); + refreshLocationMode(); + } + + @Override + public void onStop() { + mContext.unregisterReceiver(mReceiver); + } + + private void refreshLocationMode() { + // 'null' is checked inside updateState(), so no need to check here. + updateState(mPreference); + } +} diff --git a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java index eff5d4337e..6379e445f4 100644 --- a/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java @@ -5,6 +5,10 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.provider.Settings; +import androidx.lifecycle.LifecycleOwner; + +import com.android.settingslib.core.lifecycle.Lifecycle; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -21,11 +25,16 @@ public class AppLocationPermissionPreferenceControllerTest { @Mock private Context mContext; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mController = new AppLocationPermissionPreferenceController(mContext); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + mController = new AppLocationPermissionPreferenceController(mContext, mLifecycle); } @Test diff --git a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java index e380830598..806e2ecf98 100644 --- a/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationEnablerTest.java @@ -84,30 +84,31 @@ public class LocationEnablerTest { } @Test - public void onResume_shouldSetActiveAndRegisterListener() { - mEnabler.onResume(); + public void onStart_shouldSetActiveAndRegisterListener() { + mEnabler.onStart(); verify(mContext).registerReceiver(eq(mEnabler.mReceiver), eq(LocationEnabler.INTENT_FILTER_LOCATION_MODE_CHANGED)); } @Test - public void onResume_shouldRefreshLocationMode() { - mEnabler.onResume(); + public void onStart_shouldRefreshLocationMode() { + mEnabler.onStart(); verify(mEnabler).refreshLocationMode(); } @Test - public void onPause_shouldUnregisterListener() { - mEnabler.onPause(); + public void onStop_shouldUnregisterListener() { + mEnabler.onStart(); + mEnabler.onStop(); verify(mContext).unregisterReceiver(mEnabler.mReceiver); } @Test public void onReceive_shouldRefreshLocationMode() { - mEnabler.onResume(); + mEnabler.onStart(); reset(mListener); mEnabler.mReceiver.onReceive(mContext, new Intent()); -- 2.11.0