diff options
13 files changed, 508 insertions, 156 deletions
diff --git a/api/current.txt b/api/current.txt index 3e4dfd217dcc..0dd9fea94929 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21506,6 +21506,7 @@ package android.location { method public android.location.LocationProvider getProvider(java.lang.String); method public java.util.List<java.lang.String> getProviders(boolean); method public java.util.List<java.lang.String> getProviders(android.location.Criteria, boolean); + method public boolean isLocationEnabled(); method public boolean isProviderEnabled(java.lang.String); method public boolean registerGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback); method public boolean registerGnssMeasurementsCallback(android.location.GnssMeasurementsEvent.Callback, android.os.Handler); @@ -35885,11 +35886,11 @@ package android.provider { field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy"; field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility"; field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps"; - field public static final java.lang.String LOCATION_MODE = "location_mode"; - field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2 - field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3 - field public static final int LOCATION_MODE_OFF = 0; // 0x0 - field public static final int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1 + field public static final deprecated java.lang.String LOCATION_MODE = "location_mode"; + field public static final deprecated int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2 + field public static final deprecated int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3 + field public static final deprecated int LOCATION_MODE_OFF = 0; // 0x0 + field public static final deprecated int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1 field public static final deprecated java.lang.String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; field public static final deprecated java.lang.String LOCK_PATTERN_ENABLED = "lock_pattern_autolock"; field public static final deprecated java.lang.String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED = "lock_pattern_tactile_feedback_enabled"; diff --git a/api/system-current.txt b/api/system-current.txt index 66b6d990ec0c..184462c829d9 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2331,11 +2331,15 @@ package android.location { method public deprecated boolean addGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener); method public void flushGnssBatch(); method public int getGnssBatchSize(); + method public boolean isLocationEnabledForUser(android.os.UserHandle); + method public boolean isProviderEnabledForUser(java.lang.String, android.os.UserHandle); method public boolean registerGnssBatchedLocationCallback(long, boolean, android.location.BatchedLocationCallback, android.os.Handler); method public deprecated void removeGpsMeasurementListener(android.location.GpsMeasurementsEvent.Listener); method public deprecated void removeGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener); method public void requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper); method public void requestLocationUpdates(android.location.LocationRequest, android.app.PendingIntent); + method public void setLocationEnabledForUser(boolean, android.os.UserHandle); + method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle); method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b2cc18b94641..742b61f36ea6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5509,37 +5509,54 @@ public final class Settings { * Note: do not rely on this value being present in settings.db or on ContentObserver * notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION} * to receive changes in this value. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final String LOCATION_MODE = "location_mode"; - /** - * Stores the previous location mode when {@link #LOCATION_MODE} is set to - * {@link #LOCATION_MODE_OFF} - * @hide - */ - public static final String LOCATION_PREVIOUS_MODE = "location_previous_mode"; /** - * Sets all location providers to the previous states before location was turned off. - * @hide - */ - public static final int LOCATION_MODE_PREVIOUS = -1; - /** * Location access disabled. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final int LOCATION_MODE_OFF = 0; + /** * Network Location Provider disabled, but GPS and other sensors enabled. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final int LOCATION_MODE_SENSORS_ONLY = 1; + /** * Reduced power usage, such as limiting the number of GPS updates per hour. Requests * with {@link android.location.Criteria#POWER_HIGH} may be downgraded to * {@link android.location.Criteria#POWER_MEDIUM}. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final int LOCATION_MODE_BATTERY_SAVING = 2; + /** * Best-effort location computation allowed. + * + * @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To + * get the status of a location provider, use + * {@link LocationManager#isProviderEnabled(String)}. */ + @Deprecated public static final int LOCATION_MODE_HIGH_ACCURACY = 3; /** @@ -7866,7 +7883,6 @@ public final class Settings { CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES); CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS); CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE); - CLONE_TO_MANAGED_PROFILE.add(LOCATION_PREVIOUS_MODE); CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED); CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE); } @@ -7917,8 +7933,7 @@ public final class Settings { * @param provider the location provider to query * @return true if the provider is enabled * - * @deprecated use {@link #LOCATION_MODE} or - * {@link LocationManager#isProviderEnabled(String)} + * @deprecated use {@link LocationManager#isProviderEnabled(String)} */ @Deprecated public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) { @@ -7931,12 +7946,13 @@ public final class Settings { * @param provider the location provider to query * @param userId the userId to query * @return true if the provider is enabled - * @deprecated use {@link #LOCATION_MODE} or - * {@link LocationManager#isProviderEnabled(String)} + * + * @deprecated use {@link LocationManager#isProviderEnabled(String)} * @hide */ @Deprecated - public static final boolean isLocationProviderEnabledForUser(ContentResolver cr, String provider, int userId) { + public static final boolean isLocationProviderEnabledForUser( + ContentResolver cr, String provider, int userId) { String allowedProviders = Settings.Secure.getStringForUser(cr, LOCATION_PROVIDERS_ALLOWED, userId); return TextUtils.delimitedStringContains(allowedProviders, ',', provider); @@ -7947,7 +7963,8 @@ public final class Settings { * @param cr the content resolver to use * @param provider the location provider to enable or disable * @param enabled true if the provider should be enabled - * @deprecated use {@link #putInt(ContentResolver, String, int)} and {@link #LOCATION_MODE} + * @deprecated This API is deprecated. It requires WRITE_SECURE_SETTINGS permission to + * change location settings. */ @Deprecated public static final void setLocationProviderEnabled(ContentResolver cr, @@ -7963,8 +7980,8 @@ public final class Settings { * @param enabled true if the provider should be enabled * @param userId the userId for which to enable/disable providers * @return true if the value was set, false on database errors - * @deprecated use {@link #putIntForUser(ContentResolver, String, int, int)} and - * {@link #LOCATION_MODE} + * + * @deprecated use {@link LocationManager#setProviderEnabledForUser(String, boolean, int)} * @hide */ @Deprecated @@ -7985,28 +8002,6 @@ public final class Settings { } /** - * Saves the current location mode into {@link #LOCATION_PREVIOUS_MODE}. - */ - private static final boolean saveLocationModeForUser(ContentResolver cr, int userId) { - final int mode = getLocationModeForUser(cr, userId); - return putIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, mode, userId); - } - - /** - * Restores the current location mode from {@link #LOCATION_PREVIOUS_MODE}. - */ - private static final boolean restoreLocationModeForUser(ContentResolver cr, int userId) { - int mode = getIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, - LOCATION_MODE_HIGH_ACCURACY, userId); - // Make sure that the previous mode is never "off". Otherwise the user won't be able to - // turn on location any longer. - if (mode == LOCATION_MODE_OFF) { - mode = LOCATION_MODE_HIGH_ACCURACY; - } - return setLocationModeForUser(cr, mode, userId); - } - - /** * Thread-safe method for setting the location mode to one of * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. @@ -8019,18 +8014,20 @@ public final class Settings { * @return true if the value was set, false on database errors * * @throws IllegalArgumentException if mode is not one of the supported values + * + * @deprecated To enable/disable location, use + * {@link LocationManager#setLocationEnabledForUser(boolean, int)}. + * To enable/disable a specific location provider, use + * {@link LocationManager#setProviderEnabledForUser(String, boolean, int)}. */ - private static final boolean setLocationModeForUser(ContentResolver cr, int mode, - int userId) { + @Deprecated + private static boolean setLocationModeForUser( + ContentResolver cr, int mode, int userId) { synchronized (mLocationSettingsLock) { boolean gps = false; boolean network = false; switch (mode) { - case LOCATION_MODE_PREVIOUS: - // Retrieve the actual mode and set to that mode. - return restoreLocationModeForUser(cr, userId); case LOCATION_MODE_OFF: - saveLocationModeForUser(cr, userId); break; case LOCATION_MODE_SENSORS_ONLY: gps = true; @@ -8045,15 +8042,7 @@ public final class Settings { default: throw new IllegalArgumentException("Invalid location mode: " + mode); } - // Note it's important that we set the NLP mode first. The Google implementation - // of NLP clears its NLP consent setting any time it receives a - // LocationManager.PROVIDERS_CHANGED_ACTION broadcast and NLP is disabled. Also, - // it shows an NLP consent dialog any time it receives the broadcast, NLP is - // enabled, and the NLP consent is not set. If 1) we were to enable GPS first, - // 2) a setup wizard has its own NLP consent UI that sets the NLP consent setting, - // and 3) the receiver happened to complete before we enabled NLP, then the Google - // NLP would detect the attempt to enable NLP and show a redundant NLP consent - // dialog. Then the people who wrote the setup wizard would be sad. + boolean nlpSuccess = Settings.Secure.setLocationProviderEnabledForUser( cr, LocationManager.NETWORK_PROVIDER, network, userId); boolean gpsSuccess = Settings.Secure.setLocationProviderEnabledForUser( diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index ec3a6ce18cce..1a76805edb7e 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -479,7 +479,6 @@ public class SettingsBackupTest { Settings.Secure.INSTALL_NON_MARKET_APPS, Settings.Secure.LAST_SETUP_SHOWN, Settings.Secure.LOCATION_MODE, - Settings.Secure.LOCATION_PREVIOUS_MODE, Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, // Candidate? Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate? Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT, diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 0990dcccaecb..018db9a7fc9d 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -89,6 +89,10 @@ interface ILocationManager ProviderProperties getProviderProperties(String provider); String getNetworkProviderPackage(); boolean isProviderEnabled(String provider); + boolean isProviderEnabledForUser(String provider, int userId); + boolean setProviderEnabledForUser(String provider, boolean enabled, int userId); + boolean isLocationEnabledForUser(int userId); + void setLocationEnabledForUser(boolean enabled, int userId); void addTestProvider(String name, in ProviderProperties properties, String opPackageName); void removeTestProvider(String provider, String opPackageName); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index f0b2774a4276..9db9d3325dcc 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -16,7 +16,10 @@ package android.location; -import com.android.internal.location.ProviderProperties; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.LOCATION_HARDWARE; +import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import android.Manifest; import android.annotation.NonNull; @@ -24,7 +27,6 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.TestApi; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -33,17 +35,15 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; - +import com.android.internal.location.ProviderProperties; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import static android.Manifest.permission.ACCESS_COARSE_LOCATION; -import static android.Manifest.permission.ACCESS_FINE_LOCATION; -import static android.Manifest.permission.LOCATION_HARDWARE; - /** * This class provides access to the system location services. These * services allow applications to obtain periodic updates of the @@ -1171,13 +1171,57 @@ public class LocationManager { } /** + * Returns the current enabled/disabled status of location + * + * @return true if location is enabled. false if location is disabled. + */ + public boolean isLocationEnabled() { + return isLocationEnabledForUser(Process.myUserHandle()); + } + + /** + * Method for enabling or disabling location. + * + * @param enabled true to enable location. false to disable location + * @param userHandle the user to set + * @return true if the value was set, false on database errors + * + * @hide + */ + @SystemApi + @RequiresPermission(WRITE_SECURE_SETTINGS) + public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) { + try { + mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the current enabled/disabled status of location + * + * @param userHandle the user to query + * @return true location is enabled. false if location is disabled. + * + * @hide + */ + @SystemApi + public boolean isLocationEnabledForUser(UserHandle userHandle) { + try { + return mService.isLocationEnabledForUser(userHandle.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the current enabled/disabled status of the given provider. * * <p>If the user has enabled this provider in the Settings menu, true * is returned otherwise false is returned * - * <p>Callers should instead use - * {@link android.provider.Settings.Secure#LOCATION_MODE} + * <p>Callers should instead use {@link #isLocationEnabled()} * unless they depend on provider-specific APIs such as * {@link #requestLocationUpdates(String, long, float, LocationListener)}. * @@ -1202,6 +1246,64 @@ public class LocationManager { } /** + * Returns the current enabled/disabled status of the given provider and user. + * + * <p>If the user has enabled this provider in the Settings menu, true + * is returned otherwise false is returned + * + * <p>Callers should instead use {@link #isLocationEnabled()} + * unless they depend on provider-specific APIs such as + * {@link #requestLocationUpdates(String, long, float, LocationListener)}. + * + * <p> + * Before API version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this + * method would throw {@link SecurityException} if the location permissions + * were not sufficient to use the specified provider. + * + * @param provider the name of the provider + * @param userHandle the user to query + * @return true if the provider exists and is enabled + * + * @throws IllegalArgumentException if provider is null + * @hide + */ + @SystemApi + public boolean isProviderEnabledForUser(String provider, UserHandle userHandle) { + checkProvider(provider); + + try { + return mService.isProviderEnabledForUser(provider, userHandle.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Method for enabling or disabling a single location provider. + * + * @param provider the name of the provider + * @param enabled true to enable the provider. false to disable the provider + * @param userHandle the user to set + * @return true if the value was set, false on database errors + * + * @throws IllegalArgumentException if provider is null + * @hide + */ + @SystemApi + @RequiresPermission(WRITE_SECURE_SETTINGS) + public boolean setProviderEnabledForUser( + String provider, boolean enabled, UserHandle userHandle) { + checkProvider(provider); + + try { + return mService.setProviderEnabledForUser( + provider, enabled, userHandle.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Get the last known location. * * <p>This location could be very old so use diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 3c46d99906c7..d001e66acf1d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -21,10 +21,9 @@ import android.os.UserHandle; import android.os.UserManager; import android.print.PrintManager; import android.provider.Settings; - import com.android.internal.util.UserIcons; import com.android.settingslib.drawable.UserIconDrawable; - +import com.android.settingslib.wrapper.LocationManagerWrapper; import java.text.NumberFormat; public class Utils { @@ -45,6 +44,24 @@ public class Utils { com.android.internal.R.drawable.ic_wifi_signal_4 }; + public static void updateLocationEnabled(Context context, boolean enabled, int userId) { + Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION); + + final int oldMode = Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF, userId); + final int newMode = enabled + ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY + : Settings.Secure.LOCATION_MODE_OFF; + intent.putExtra(CURRENT_MODE_KEY, oldMode); + intent.putExtra(NEW_MODE_KEY, newMode); + context.sendBroadcastAsUser( + intent, UserHandle.of(userId), android.Manifest.permission.WRITE_SECURE_SETTINGS); + LocationManager locationManager = + (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + LocationManagerWrapper wrapper = new LocationManagerWrapper(locationManager); + wrapper.setLocationEnabledForUser(enabled, UserHandle.of(userId)); + } + public static boolean updateLocationMode(Context context, int oldMode, int newMode, int userId) { Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION); intent.putExtra(CURRENT_MODE_KEY, oldMode); diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java new file mode 100644 index 000000000000..1a268a608e5d --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/LocationManagerWrapper.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 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.settingslib.wrapper; + +import android.location.LocationManager; +import android.os.UserHandle; + +/** + * This class replicates some methods of android.location.LocationManager that are new and not + * yet available in our current version of Robolectric. It provides a thin wrapper to call the real + * methods in production and a mock in tests. + */ +public class LocationManagerWrapper { + + private LocationManager mLocationManager; + + public LocationManagerWrapper(LocationManager locationManager) { + mLocationManager = locationManager; + } + + /** Returns the real {@code LocationManager} object */ + public LocationManager getLocationManager() { + return mLocationManager; + } + + /** Wraps {@code LocationManager.isProviderEnabled} method */ + public boolean isProviderEnabled(String provider) { + return mLocationManager.isProviderEnabled(provider); + } + + /** Wraps {@code LocationManager.setProviderEnabledForUser} method */ + public void setProviderEnabledForUser(String provider, boolean enabled, UserHandle userHandle) { + mLocationManager.setProviderEnabledForUser(provider, enabled, userHandle); + } + + /** Wraps {@code LocationManager.isLocationEnabled} method */ + public boolean isLocationEnabled() { + return mLocationManager.isLocationEnabled(); + } + + /** Wraps {@code LocationManager.isLocationEnabledForUser} method */ + public boolean isLocationEnabledForUser(UserHandle userHandle) { + return mLocationManager.isLocationEnabledForUser(userHandle); + } + + /** Wraps {@code LocationManager.setLocationEnabledForUser} method */ + public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) { + mLocationManager.setLocationEnabledForUser(enabled, userHandle); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 976bbee64208..327c1c8fd1d8 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -15,15 +15,26 @@ */ package com.android.settingslib; +import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.location.LocationManager; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Secure; import android.text.TextUtils; +import com.android.settingslib.wrapper.LocationManagerWrapper; import java.util.HashMap; import java.util.Map; import org.junit.Before; @@ -31,20 +42,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; - -import static android.Manifest.permission.WRITE_SECURE_SETTINGS; -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.Resources; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowSettings; @@ -53,7 +54,9 @@ import org.robolectric.shadows.ShadowSettings; @Config( manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, - shadows = {UtilsTest.ShadowSecure.class}) + shadows = { + UtilsTest.ShadowSecure.class, + UtilsTest.ShadowLocationManagerWrapper.class}) public class UtilsTest { private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100}; private static final String PERCENTAGE_0 = "0%"; @@ -63,10 +66,14 @@ public class UtilsTest { private static final String PERCENTAGE_100 = "100%"; private Context mContext; + @Mock + private LocationManager mLocationManager; @Before public void setUp() { + MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager); ShadowSecure.reset(); } @@ -86,6 +93,17 @@ public class UtilsTest { } @Test + public void testUpdateLocationEnabled_sendBroadcast() { + int currentUserId = ActivityManager.getCurrentUser(); + Utils.updateLocationEnabled(mContext, true, currentUserId); + + verify(mContext).sendBroadcastAsUser( + argThat(actionMatches(LocationManager.MODE_CHANGING_ACTION)), + ArgumentMatchers.eq(UserHandle.of(currentUserId)), + ArgumentMatchers.eq(WRITE_SECURE_SETTINGS)); + } + + @Test public void testFormatPercentage_RoundTrue_RoundUpIfPossible() { final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, @@ -137,8 +155,26 @@ public class UtilsTest { return true; } + @Implementation + public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { + if (map.containsKey(name)) { + return map.get(name); + } else { + return def; + } + } + public static void reset() { map.clear(); } } + + @Implements(value = LocationManagerWrapper.class) + public static class ShadowLocationManagerWrapper { + + @Implementation + public void setLocationEnabledForUser(boolean enabled, UserHandle userHandle) { + // Do nothing + } + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index fc765f4d3335..cfd33a19baf9 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -16,6 +16,7 @@ package com.android.providers.settings; +import android.os.Process; import com.android.internal.R; import com.android.internal.app.LocalePicker; import com.android.internal.annotations.VisibleForTesting; @@ -288,12 +289,12 @@ public class SettingsHelper { } final String GPS = LocationManager.GPS_PROVIDER; boolean enabled = - GPS.equals(value) || + GPS.equals(value) || value.startsWith(GPS + ",") || value.endsWith("," + GPS) || value.contains("," + GPS + ","); - Settings.Secure.setLocationProviderEnabled( - mContext.getContentResolver(), GPS, enabled); + LocationManager lm = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + lm.setProviderEnabledForUser(GPS, enabled, Process.myUserHandle()); } private void setSoundEffects(boolean enable) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 87ed7eb705e9..ee5017281e30 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1220,9 +1220,6 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Secure.LOCATION_MODE, SecureSettingsProto.LOCATION_MODE); - dumpSetting(s, p, - Settings.Secure.LOCATION_PREVIOUS_MODE, - SecureSettingsProto.LOCATION_PREVIOUS_MODE); // Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS intentionally excluded since it's deprecated. dumpSetting(s, p, Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 4ee4ef492ec1..0b666a6106e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -16,11 +16,12 @@ package com.android.systemui.statusbar.policy; +import static com.android.settingslib.Utils.updateLocationEnabled; + import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.StatusBarManager; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -28,19 +29,14 @@ import android.location.LocationManager; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.UserHandle; import android.os.UserManager; -import android.provider.Settings; import android.support.annotation.VisibleForTesting; - -import com.android.systemui.R; import com.android.systemui.util.Utils; - import java.util.ArrayList; import java.util.List; -import static com.android.settingslib.Utils.updateLocationMode; - /** * A controller to manage changes of location related states and update the views accordingly. */ @@ -101,32 +97,27 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio * @return true if attempt to change setting was successful. */ public boolean setLocationEnabled(boolean enabled) { + // QuickSettings always runs as the owner, so specifically set the settings + // for the current foreground user. int currentUserId = ActivityManager.getCurrentUser(); if (isUserLocationRestricted(currentUserId)) { return false; } - final ContentResolver cr = mContext.getContentResolver(); // When enabling location, a user consent dialog will pop up, and the // setting won't be fully enabled until the user accepts the agreement. - int currentMode = Settings.Secure.getIntForUser(cr, Settings.Secure.LOCATION_MODE, - Settings.Secure.LOCATION_MODE_OFF, currentUserId); - int mode = enabled - ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF; - // QuickSettings always runs as the owner, so specifically set the settings - // for the current foreground user. - return updateLocationMode(mContext, currentMode, mode, currentUserId); + updateLocationEnabled(mContext, enabled, currentUserId); + return true; } /** - * Returns true if location isn't disabled in settings. + * Returns true if location is enabled in settings. */ public boolean isLocationEnabled() { - ContentResolver resolver = mContext.getContentResolver(); // QuickSettings always runs as the owner, so specifically retrieve the settings // for the current foreground user. - int mode = Settings.Secure.getIntForUser(resolver, Settings.Secure.LOCATION_MODE, - Settings.Secure.LOCATION_MODE_OFF, ActivityManager.getCurrentUser()); - return mode != Settings.Secure.LOCATION_MODE_OFF; + LocationManager locationManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + return locationManager.isLocationEnabledForUser(Process.myUserHandle()); } @Override diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 6c63f43234ab..5a4f7cab3ed6 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -16,36 +16,10 @@ package com.android.server; -import android.app.ActivityManager; -import android.annotation.NonNull; -import android.util.ArrayMap; -import android.util.ArraySet; - -import com.android.internal.content.PackageMonitor; -import com.android.internal.location.ProviderProperties; -import com.android.internal.location.ProviderRequest; -import com.android.internal.os.BackgroundThread; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.DumpUtils; -import com.android.server.location.ActivityRecognitionProxy; -import com.android.server.location.FlpHardwareProvider; -import com.android.server.location.FusedProxy; -import com.android.server.location.GeocoderProxy; -import com.android.server.location.GeofenceManager; -import com.android.server.location.GeofenceProxy; -import com.android.server.location.GnssLocationProvider; -import com.android.server.location.GnssMeasurementsProvider; -import com.android.server.location.GnssNavigationMessageProvider; -import com.android.server.location.LocationBlacklist; -import com.android.server.location.LocationFudger; -import com.android.server.location.LocationProviderInterface; -import com.android.server.location.LocationProviderProxy; -import com.android.server.location.LocationRequestStatistics; -import com.android.server.location.LocationRequestStatistics.PackageProviderKey; -import com.android.server.location.LocationRequestStatistics.PackageStatistics; -import com.android.server.location.MockProvider; -import com.android.server.location.PassiveProvider; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import android.annotation.NonNull; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -56,8 +30,8 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.Signature; import android.content.res.Resources; @@ -69,10 +43,10 @@ import android.location.GeocoderParams; import android.location.Geofence; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; +import android.location.IGnssNavigationMessageListener; import android.location.IGnssStatusListener; import android.location.IGnssStatusProvider; import android.location.IGpsGeofenceHardware; -import android.location.IGnssNavigationMessageListener; import android.location.ILocationListener; import android.location.ILocationManager; import android.location.INetInitiatedListener; @@ -95,10 +69,35 @@ import android.os.UserManager; import android.os.WorkSource; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Slog; - +import com.android.internal.content.PackageMonitor; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.DumpUtils; +import com.android.server.location.ActivityRecognitionProxy; +import com.android.server.location.FlpHardwareProvider; +import com.android.server.location.FusedProxy; +import com.android.server.location.GeocoderProxy; +import com.android.server.location.GeofenceManager; +import com.android.server.location.GeofenceProxy; +import com.android.server.location.GnssLocationProvider; +import com.android.server.location.GnssMeasurementsProvider; +import com.android.server.location.GnssNavigationMessageProvider; +import com.android.server.location.LocationBlacklist; +import com.android.server.location.LocationFudger; +import com.android.server.location.LocationProviderInterface; +import com.android.server.location.LocationProviderProxy; +import com.android.server.location.LocationRequestStatistics; +import com.android.server.location.LocationRequestStatistics.PackageProviderKey; +import com.android.server.location.LocationRequestStatistics.PackageStatistics; +import com.android.server.location.MockProvider; +import com.android.server.location.PassiveProvider; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -1378,10 +1377,7 @@ public class LocationManagerService extends ILocationManager.Stub { if (mDisabledProviders.contains(provider)) { return false; } - // Use system settings - ContentResolver resolver = mContext.getContentResolver(); - - return Settings.Secure.isLocationProviderEnabledForUser(resolver, provider, mCurrentUserId); + return isLocationProviderEnabledForUser(provider, mCurrentUserId); } /** @@ -1400,6 +1396,23 @@ public class LocationManagerService extends ILocationManager.Stub { } /** + * Returns "true" if access to the specified location provider is allowed by the specified + * user's settings. Access to all location providers is forbidden to non-location-provider + * processes belonging to background users. + * + * @param provider the name of the location provider + * @param uid the requestor's UID + * @param userId the user id to query + */ + private boolean isAllowedByUserSettingsLockedForUser( + String provider, int uid, int userId) { + if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) { + return false; + } + return isLocationProviderEnabledForUser(provider, userId); + } + + /** * Returns the permission string associated with the specified resolution level. * * @param resolutionLevel the resolution level @@ -1425,10 +1438,10 @@ public class LocationManagerService extends ILocationManager.Stub { */ private int getAllowedResolutionLevel(int pid, int uid) { if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, - pid, uid) == PackageManager.PERMISSION_GRANTED) { + pid, uid) == PERMISSION_GRANTED) { return RESOLUTION_LEVEL_FINE; } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, - pid, uid) == PackageManager.PERMISSION_GRANTED) { + pid, uid) == PERMISSION_GRANTED) { return RESOLUTION_LEVEL_COARSE; } else { return RESOLUTION_LEVEL_NONE; @@ -2053,7 +2066,7 @@ public class LocationManagerService extends ILocationManager.Stub { } boolean callerHasLocationHardwarePermission = mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) - == PackageManager.PERMISSION_GRANTED; + == PERMISSION_GRANTED; LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel, callerHasLocationHardwarePermission); @@ -2326,7 +2339,7 @@ public class LocationManagerService extends ILocationManager.Stub { // Require that caller can manage given document boolean callerHasLocationHardwarePermission = mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE) - == PackageManager.PERMISSION_GRANTED; + == PERMISSION_GRANTED; LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel, callerHasLocationHardwarePermission); @@ -2476,7 +2489,7 @@ public class LocationManagerService extends ILocationManager.Stub { // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) - != PackageManager.PERMISSION_GRANTED)) { + != PERMISSION_GRANTED)) { throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission"); } @@ -2546,8 +2559,64 @@ public class LocationManagerService extends ILocationManager.Stub { return null; } + /** + * Method for enabling or disabling location. + * + * @param enabled true to enable location. false to disable location + * @param userId the user id to set + */ + @Override + public void setLocationEnabledForUser(boolean enabled, int userId) { + // Check INTERACT_ACROSS_USERS permission if userId is not current user id. + checkInteractAcrossUsersPermission(userId); + + // enable all location providers + synchronized (mLock) { + for(String provider : getAllProviders()) { + setProviderEnabledForUser(provider, enabled, userId); + } + } + } + + /** + * Returns the current enabled/disabled status of location + * + * @param userId the user id to query + * @return true if location is enabled. false if location is disabled. + */ + @Override + public boolean isLocationEnabledForUser(int userId) { + + // Check INTERACT_ACROSS_USERS permission if userId is not current user id. + checkInteractAcrossUsersPermission(userId); + + synchronized (mLock) { + for (String provider : getAllProviders()) { + if (isProviderEnabledForUser(provider, userId)) { + return true; + } + } + return false; + } + } + @Override public boolean isProviderEnabled(String provider) { + return isProviderEnabledForUser(provider, UserHandle.myUserId()); + } + + /** + * Method for determining if a location provider is enabled. + * + * @param provider the location provider to query + * @param userId the user id to query + * @return true if the provider is enabled + */ + @Override + public boolean isProviderEnabledForUser(String provider, int userId) { + // Check INTERACT_ACROSS_USERS permission if userId is not current user id. + checkInteractAcrossUsersPermission(userId); + // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, // so we discourage its use if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; @@ -2557,7 +2626,8 @@ public class LocationManagerService extends ILocationManager.Stub { try { synchronized (mLock) { LocationProviderInterface p = mProvidersByName.get(provider); - return p != null && isAllowedByUserSettingsLocked(provider, uid); + return p != null + && isAllowedByUserSettingsLockedForUser(provider, uid, userId); } } finally { Binder.restoreCallingIdentity(identity); @@ -2565,6 +2635,83 @@ public class LocationManagerService extends ILocationManager.Stub { } /** + * Method for enabling or disabling a single location provider. + * + * @param provider the name of the provider + * @param enabled true to enable the provider. false to disable the provider + * @param userId the user id to set + * @return true if the value was set successfully. false on failure. + */ + @Override + public boolean setProviderEnabledForUser( + String provider, boolean enabled, int userId) { + mContext.enforceCallingPermission( + android.Manifest.permission.WRITE_SECURE_SETTINGS, + "Requires WRITE_SECURE_SETTINGS permission"); + + // Check INTERACT_ACROSS_USERS permission if userId is not current user id. + checkInteractAcrossUsersPermission(userId); + + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + // to ensure thread safety, we write the provider name with a '+' or '-' + // and let the SettingsProvider handle it rather than reading and modifying + // the list of enabled providers. + if (enabled) { + provider = "+" + provider; + } else { + provider = "-" + provider; + } + return Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + provider, + userId); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Read location provider status from Settings.Secure + * + * @param provider the location provider to query + * @param userId the user id to query + * @return true if the provider is enabled + */ + private boolean isLocationProviderEnabledForUser(String provider, int userId) { + long identity = Binder.clearCallingIdentity(); + try { + // Use system settings + ContentResolver cr = mContext.getContentResolver(); + String allowedProviders = Settings.Secure.getStringForUser( + cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, userId); + return TextUtils.delimitedStringContains(allowedProviders, ',', provider); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Method for checking INTERACT_ACROSS_USERS permission if specified user id is not the same as + * current user id + * + * @param userId the user id to get or set value + */ + private void checkInteractAcrossUsersPermission(int userId) { + int uid = Binder.getCallingUid(); + if (UserHandle.getUserId(uid) != userId) { + if (ActivityManager.checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, uid, -1, true) + != PERMISSION_GRANTED) { + throw new SecurityException("Requires INTERACT_ACROSS_USERS permission"); + } + } + } + + /** * Returns "true" if the UID belongs to a bound location provider. * * @param uid the uid @@ -2585,7 +2732,7 @@ public class LocationManagerService extends ILocationManager.Stub { private void checkCallerIsProvider() { if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER) - == PackageManager.PERMISSION_GRANTED) { + == PERMISSION_GRANTED) { return; } |