Display app stats for location permission
Bug: 120221631 Test: manually Change-Id: I53f43079807759c50eeb62029bb0d8d1f84e1118
This commit is contained in:
@@ -827,8 +827,15 @@
|
|||||||
<string name="location_settings_title">Location</string>
|
<string name="location_settings_title">Location</string>
|
||||||
<!-- Used in the location settings to control turning on/off the feature entirely -->
|
<!-- Used in the location settings to control turning on/off the feature entirely -->
|
||||||
<string name="location_settings_master_switch_title">Use location</string>
|
<string name="location_settings_master_switch_title">Use location</string>
|
||||||
<!-- Summary for Location settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
|
<!-- Summary for Location settings when location is off [CHAR LIMIT=NONE] -->
|
||||||
<string name="location_settings_summary">Scanning, location history</string>
|
<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] -->
|
<!-- 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="account_settings_title">Accounts</string>
|
||||||
@@ -3630,7 +3637,22 @@
|
|||||||
<string name="managed_profile_location_switch_title">Location for work profile</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
|
<!-- [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 -->
|
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 -->
|
<!-- [CHAR LIMIT=50] Location settings screen, sub category for recent location access -->
|
||||||
<string name="location_category_recent_location_access">Recent location access</string>
|
<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 -->
|
<!-- [CHAR LIMIT=30] Location settings screen, button to bring the user to view the details of recent location access -->
|
||||||
|
@@ -93,10 +93,11 @@
|
|||||||
<Preference
|
<Preference
|
||||||
android:key="top_level_location"
|
android:key="top_level_location"
|
||||||
android:title="@string/location_settings_title"
|
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:icon="@drawable/ic_homepage_location"
|
||||||
android:order="-50"
|
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
|
<Preference
|
||||||
android:key="top_level_security"
|
android:key="top_level_security"
|
||||||
@@ -149,4 +150,4 @@
|
|||||||
android:order="100"
|
android:order="100"
|
||||||
settings:controller="com.android.settings.support.SupportPreferenceController"/>
|
settings:controller="com.android.settings.support.SupportPreferenceController"/>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@@ -1,18 +1,38 @@
|
|||||||
package com.android.settings.location;
|
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.content.Context;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.permission.RuntimePermissionPresenter;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
import com.android.settings.core.PreferenceControllerMixin;
|
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
|
public class AppLocationPermissionPreferenceController extends
|
||||||
AbstractPreferenceController implements PreferenceControllerMixin {
|
LocationBasePreferenceController implements PreferenceControllerMixin {
|
||||||
|
|
||||||
private static final String KEY_APP_LEVEL_PERMISSIONS = "app_level_permissions";
|
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) {
|
public AppLocationPermissionPreferenceController(Context context, Lifecycle lifecycle) {
|
||||||
super(context);
|
super(context, lifecycle);
|
||||||
|
mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -25,4 +45,53 @@ public class AppLocationPermissionPreferenceController extends
|
|||||||
return Settings.Global.getInt(mContext.getContentResolver(),
|
return Settings.Global.getInt(mContext.getContentResolver(),
|
||||||
Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED, 1) == 1;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -35,15 +35,15 @@ import com.android.settingslib.RestrictedLockUtils;
|
|||||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||||
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
import com.android.settingslib.core.lifecycle.LifecycleObserver;
|
||||||
import com.android.settingslib.core.lifecycle.events.OnPause;
|
import com.android.settingslib.core.lifecycle.events.OnStart;
|
||||||
import com.android.settingslib.core.lifecycle.events.OnResume;
|
import com.android.settingslib.core.lifecycle.events.OnStop;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that listens to location settings change and modifies location settings
|
* A class that listens to location settings change and modifies location settings
|
||||||
* settings.
|
* settings.
|
||||||
*/
|
*/
|
||||||
public class LocationEnabler implements LifecycleObserver, OnResume, OnPause {
|
public class LocationEnabler implements LifecycleObserver, OnStart, OnStop {
|
||||||
|
|
||||||
private static final String TAG = "LocationEnabler";
|
private static final String TAG = "LocationEnabler";
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -73,7 +73,7 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onStart() {
|
||||||
if (mReceiver == null) {
|
if (mReceiver == null) {
|
||||||
mReceiver = new BroadcastReceiver() {
|
mReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
@@ -90,12 +90,8 @@ public class LocationEnabler implements LifecycleObserver, OnResume, OnPause {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onStop() {
|
||||||
try {
|
mContext.unregisterReceiver(mReceiver);
|
||||||
mContext.unregisterReceiver(mReceiver);
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
// Ignore exceptions caused by race condition
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshLocationMode() {
|
void refreshLocationMode() {
|
||||||
|
@@ -122,7 +122,7 @@ public class LocationSettings extends DashboardFragment {
|
|||||||
private static List<AbstractPreferenceController> buildPreferenceControllers(
|
private static List<AbstractPreferenceController> buildPreferenceControllers(
|
||||||
Context context, LocationSettings fragment, Lifecycle lifecycle) {
|
Context context, LocationSettings fragment, Lifecycle lifecycle) {
|
||||||
final List<AbstractPreferenceController> controllers = new ArrayList<>();
|
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 LocationForWorkPreferenceController(context, lifecycle));
|
||||||
controllers.add(new RecentLocationAccessPreferenceController(context));
|
controllers.add(new RecentLocationAccessPreferenceController(context));
|
||||||
controllers.add(new LocationScanningPreferenceController(context));
|
controllers.add(new LocationScanningPreferenceController(context));
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,10 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
|
||||||
|
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -21,11 +25,16 @@ public class AppLocationPermissionPreferenceControllerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
|
|
||||||
|
private LifecycleOwner mLifecycleOwner;
|
||||||
|
private Lifecycle mLifecycle;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mContext = RuntimeEnvironment.application;
|
mContext = RuntimeEnvironment.application;
|
||||||
mController = new AppLocationPermissionPreferenceController(mContext);
|
mLifecycleOwner = () -> mLifecycle;
|
||||||
|
mLifecycle = new Lifecycle(mLifecycleOwner);
|
||||||
|
mController = new AppLocationPermissionPreferenceController(mContext, mLifecycle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@@ -84,30 +84,31 @@ public class LocationEnablerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onResume_shouldSetActiveAndRegisterListener() {
|
public void onStart_shouldSetActiveAndRegisterListener() {
|
||||||
mEnabler.onResume();
|
mEnabler.onStart();
|
||||||
|
|
||||||
verify(mContext).registerReceiver(eq(mEnabler.mReceiver),
|
verify(mContext).registerReceiver(eq(mEnabler.mReceiver),
|
||||||
eq(LocationEnabler.INTENT_FILTER_LOCATION_MODE_CHANGED));
|
eq(LocationEnabler.INTENT_FILTER_LOCATION_MODE_CHANGED));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onResume_shouldRefreshLocationMode() {
|
public void onStart_shouldRefreshLocationMode() {
|
||||||
mEnabler.onResume();
|
mEnabler.onStart();
|
||||||
|
|
||||||
verify(mEnabler).refreshLocationMode();
|
verify(mEnabler).refreshLocationMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onPause_shouldUnregisterListener() {
|
public void onStop_shouldUnregisterListener() {
|
||||||
mEnabler.onPause();
|
mEnabler.onStart();
|
||||||
|
mEnabler.onStop();
|
||||||
|
|
||||||
verify(mContext).unregisterReceiver(mEnabler.mReceiver);
|
verify(mContext).unregisterReceiver(mEnabler.mReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onReceive_shouldRefreshLocationMode() {
|
public void onReceive_shouldRefreshLocationMode() {
|
||||||
mEnabler.onResume();
|
mEnabler.onStart();
|
||||||
reset(mListener);
|
reset(mListener);
|
||||||
mEnabler.mReceiver.onReceive(mContext, new Intent());
|
mEnabler.mReceiver.onReceive(mContext, new Intent());
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user