OSDN Git Service

Display app stats for location permission
authorLifu Tang <lifu@google.com>
Tue, 11 Dec 2018 21:50:34 +0000 (13:50 -0800)
committerLifu Tang <lifu@google.com>
Tue, 18 Dec 2018 05:13:08 +0000 (21:13 -0800)
Bug: 120221631
Test: manually
Change-Id: I53f43079807759c50eeb62029bb0d8d1f84e1118

res/values/strings.xml
res/xml/top_level_settings.xml
src/com/android/settings/location/AppLocationPermissionPreferenceController.java
src/com/android/settings/location/LocationEnabler.java
src/com/android/settings/location/LocationSettings.java
src/com/android/settings/location/TopLevelLocationPreferenceController.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/location/AppLocationPermissionPreferenceControllerTest.java
tests/robotests/src/com/android/settings/location/LocationEnablerTest.java

index 4abea44..be9cc14 100644 (file)
     <string name="location_settings_title">Location</string>
     <!-- Used in the location settings to control turning on/off the feature entirely -->
     <string name="location_settings_master_switch_title">Use location</string>
-    <!-- Summary for Location settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
-    <string name="location_settings_summary">Scanning, location history</string>
+    <!-- Summary for Location settings when location is off [CHAR LIMIT=NONE] -->
+    <string name="location_settings_summary_location_off">Off</string>
+    <!-- Summary for Location settings when location is on, explaining how many apps have location permission [CHAR LIMIT=NONE]-->
+    <plurals name="location_settings_summary_location_on">
+        <item quantity="one">On - <xliff:g id="count">%1$d</xliff:g> app can access location</item>
+        <item quantity="other">On - <xliff:g id="count">%1$d</xliff:g> apps can access location</item>
+    </plurals>
+    <!-- Location settings, loading the number of apps which have location permission [CHAR LIMIT=30] -->
+    <string name="location_settings_loading_app_permission_stats">Loading\u2026</string>
 
     <!-- Main Settings screen setting option title for the item to take you to the accounts screen [CHAR LIMIT=22] -->
     <string name="account_settings_title">Accounts</string>
     <string name="managed_profile_location_switch_title">Location for work profile</string>
     <!-- [CHAR LIMIT=30] Location settings screen. It's a link that directs the user to a page that
       shows the location permission setting for each installed app -->
-    <string name="location_app_level_permissions">App-level permissions</string>
+    <string name="location_app_level_permissions">App permission</string>
+    <!-- Summary for app permission on Location settings page when location is off [CHAR LIMIT=NONE] -->
+    <string name="location_app_permission_summary_location_off">Location is off</string>
+    <!-- Summary for Location settings when location is on, explaining how many apps have location permission [CHAR LIMIT=NONE]-->
+    <plurals name="location_app_permission_summary_location_on">
+        <item quantity="one">
+            <xliff:g id="background_location_app_count">%1$d</xliff:g>
+            of
+            <xliff:g id="total_location_app_count">%2$d</xliff:g>
+            app has unlimited access</item>
+        <item quantity="other">
+            <xliff:g id="background_location_app_count">%1$d</xliff:g>
+            of
+            <xliff:g id="total_location_app_count">%2$d</xliff:g>
+            apps have unlimited access</item>
+    </plurals>
     <!-- [CHAR LIMIT=50] Location settings screen, sub category for recent location access -->
     <string name="location_category_recent_location_access">Recent location access</string>
     <!-- [CHAR LIMIT=30] Location settings screen, button to bring the user to view the details of recent location access -->
index 03e32dc..9f4f902 100644 (file)
     <Preference
         android:key="top_level_location"
         android:title="@string/location_settings_title"
-        android:summary="@string/location_settings_summary"
+        android:summary="@string/location_settings_loading_app_permission_stats"
         android:icon="@drawable/ic_homepage_location"
         android:order="-50"
-        android:fragment="com.android.settings.location.LocationSettings"/>
+        android:fragment="com.android.settings.location.LocationSettings"
+        settings:controller="com.android.settings.location.TopLevelLocationPreferenceController"/>
 
     <Preference
         android:key="top_level_security"
         android:order="100"
         settings:controller="com.android.settings.support.SupportPreferenceController"/>
 
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
index f920fdc..5bfc584 100644 (file)
@@ -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);
+    }
 }
index 20c2280..e1bdf16 100644 (file)
@@ -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() {
index 8a92f47..4112340 100644 (file)
@@ -122,7 +122,7 @@ public class LocationSettings extends DashboardFragment {
     private static List<AbstractPreferenceController> buildPreferenceControllers(
             Context context, LocationSettings fragment, Lifecycle lifecycle) {
         final List<AbstractPreferenceController> 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 (file)
index 0000000..d0bd9a9
--- /dev/null
@@ -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);
+    }
+}
index eff5d43..6379e44 100644 (file)
@@ -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
index e380830..806e2ec 100644 (file)
@@ -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());