blob: 3e8da7d7478abd30557c07ba341079251f697c11 [file] [log] [blame]
/*
* Copyright (C) 2020 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.server.location.injector;
import static android.location.LocationDeviceConfig.IGNORE_SETTINGS_ALLOWLIST;
import static android.provider.Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS;
import static android.provider.Settings.Secure.LOCATION_COARSE_ACCURACY_M;
import static android.provider.Settings.Secure.LOCATION_MODE;
import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.PackageTagsList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.SystemConfig;
import java.io.FileDescriptor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;
/**
* Provides accessors and listeners for all location related settings.
*/
public class SystemSettingsHelper extends SettingsHelper {
private static final String LOCATION_PACKAGE_BLACKLIST = "locationPackagePrefixBlacklist";
private static final String LOCATION_PACKAGE_WHITELIST = "locationPackagePrefixWhitelist";
private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
private static final long DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS =
30 * 60 * 1000;
private static final float DEFAULT_COARSE_LOCATION_ACCURACY_M = 2000.0f;
private final Context mContext;
private final IntegerSecureSetting mLocationMode;
private final LongGlobalSetting mBackgroundThrottleIntervalMs;
private final BooleanGlobalSetting mGnssMeasurementFullTracking;
private final StringListCachedSecureSetting mLocationPackageBlacklist;
private final StringListCachedSecureSetting mLocationPackageWhitelist;
private final StringSetCachedGlobalSetting mBackgroundThrottlePackageWhitelist;
private final PackageTagsListSetting mIgnoreSettingsPackageAllowlist;
public SystemSettingsHelper(Context context) {
mContext = context;
mLocationMode = new IntegerSecureSetting(context, LOCATION_MODE, FgThread.getHandler());
mBackgroundThrottleIntervalMs = new LongGlobalSetting(context,
LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, FgThread.getHandler());
mGnssMeasurementFullTracking = new BooleanGlobalSetting(context,
ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, FgThread.getHandler());
mLocationPackageBlacklist = new StringListCachedSecureSetting(context,
LOCATION_PACKAGE_BLACKLIST, FgThread.getHandler());
mLocationPackageWhitelist = new StringListCachedSecureSetting(context,
LOCATION_PACKAGE_WHITELIST, FgThread.getHandler());
mBackgroundThrottlePackageWhitelist = new StringSetCachedGlobalSetting(context,
LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
() -> SystemConfig.getInstance().getAllowUnthrottledLocation(),
FgThread.getHandler());
mIgnoreSettingsPackageAllowlist = new PackageTagsListSetting(
IGNORE_SETTINGS_ALLOWLIST,
() -> SystemConfig.getInstance().getAllowIgnoreLocationSettings());
}
/** Called when system is ready. */
public void onSystemReady() {
mLocationMode.register();
mBackgroundThrottleIntervalMs.register();
mLocationPackageBlacklist.register();
mLocationPackageWhitelist.register();
mBackgroundThrottlePackageWhitelist.register();
mIgnoreSettingsPackageAllowlist.register();
}
@Override
public boolean isLocationEnabled(int userId) {
return mLocationMode.getValueForUser(LOCATION_MODE_OFF, userId) != LOCATION_MODE_OFF;
}
@Override
public void setLocationEnabled(boolean enabled, int userId) {
final long identity = Binder.clearCallingIdentity();
try {
Settings.Secure.putIntForUser(
mContext.getContentResolver(),
Settings.Secure.LOCATION_MODE,
enabled
? Settings.Secure.LOCATION_MODE_ON
: Settings.Secure.LOCATION_MODE_OFF,
userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void addOnLocationEnabledChangedListener(UserSettingChangedListener listener) {
mLocationMode.addListener(listener);
}
@Override
public void removeOnLocationEnabledChangedListener(UserSettingChangedListener listener) {
mLocationMode.removeListener(listener);
}
@Override
public long getBackgroundThrottleIntervalMs() {
return mBackgroundThrottleIntervalMs.getValue(DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
}
@Override
public void addOnBackgroundThrottleIntervalChangedListener(
GlobalSettingChangedListener listener) {
mBackgroundThrottleIntervalMs.addListener(listener);
}
@Override
public void removeOnBackgroundThrottleIntervalChangedListener(
GlobalSettingChangedListener listener) {
mBackgroundThrottleIntervalMs.removeListener(listener);
}
@Override
public boolean isLocationPackageBlacklisted(int userId, String packageName) {
List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(userId);
if (locationPackageBlacklist.isEmpty()) {
return false;
}
List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(userId);
for (String locationWhitelistPackage : locationPackageWhitelist) {
if (packageName.startsWith(locationWhitelistPackage)) {
return false;
}
}
for (String locationBlacklistPackage : locationPackageBlacklist) {
if (packageName.startsWith(locationBlacklistPackage)) {
return true;
}
}
return false;
}
@Override
public void addOnLocationPackageBlacklistChangedListener(
UserSettingChangedListener listener) {
mLocationPackageBlacklist.addListener(listener);
mLocationPackageWhitelist.addListener(listener);
}
@Override
public void removeOnLocationPackageBlacklistChangedListener(
UserSettingChangedListener listener) {
mLocationPackageBlacklist.removeListener(listener);
mLocationPackageWhitelist.removeListener(listener);
}
@Override
public Set<String> getBackgroundThrottlePackageWhitelist() {
return mBackgroundThrottlePackageWhitelist.getValue();
}
@Override
public void addOnBackgroundThrottlePackageWhitelistChangedListener(
GlobalSettingChangedListener listener) {
mBackgroundThrottlePackageWhitelist.addListener(listener);
}
@Override
public void removeOnBackgroundThrottlePackageWhitelistChangedListener(
GlobalSettingChangedListener listener) {
mBackgroundThrottlePackageWhitelist.removeListener(listener);
}
@Override
public boolean isGnssMeasurementsFullTrackingEnabled() {
return mGnssMeasurementFullTracking.getValue(false);
}
@Override
public void addOnGnssMeasurementsFullTrackingEnabledChangedListener(
GlobalSettingChangedListener listener) {
mGnssMeasurementFullTracking.addListener(listener);
}
@Override
public void removeOnGnssMeasurementsFullTrackingEnabledChangedListener(
GlobalSettingChangedListener listener) {
mGnssMeasurementFullTracking.removeListener(listener);
}
@Override
public PackageTagsList getIgnoreSettingsAllowlist() {
return mIgnoreSettingsPackageAllowlist.getValue();
}
@Override
public void addIgnoreSettingsAllowlistChangedListener(
GlobalSettingChangedListener listener) {
mIgnoreSettingsPackageAllowlist.addListener(listener);
}
@Override
public void removeIgnoreSettingsAllowlistChangedListener(
GlobalSettingChangedListener listener) {
mIgnoreSettingsPackageAllowlist.removeListener(listener);
}
@Override
public long getBackgroundThrottleProximityAlertIntervalMs() {
final long identity = Binder.clearCallingIdentity();
try {
return Settings.Global.getLong(mContext.getContentResolver(),
LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public float getCoarseLocationAccuracyM() {
final long identity = Binder.clearCallingIdentity();
final ContentResolver cr = mContext.getContentResolver();
try {
return Settings.Secure.getFloatForUser(
cr,
LOCATION_COARSE_ACCURACY_M,
DEFAULT_COARSE_LOCATION_ACCURACY_M,
cr.getUserId());
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
int[] userIds;
try {
userIds = ActivityManager.getService().getRunningUserIds();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
ipw.print("Location Setting: ");
ipw.increaseIndent();
if (userIds.length > 1) {
ipw.println();
for (int userId : userIds) {
ipw.print("[u");
ipw.print(userId);
ipw.print("] ");
ipw.println(isLocationEnabled(userId));
}
} else {
ipw.println(isLocationEnabled(userIds[0]));
}
ipw.decreaseIndent();
ipw.println("Location Allow/Deny Packages:");
ipw.increaseIndent();
if (userIds.length > 1) {
for (int userId : userIds) {
List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
userId);
if (locationPackageBlacklist.isEmpty()) {
continue;
}
ipw.print("user ");
ipw.print(userId);
ipw.println(":");
ipw.increaseIndent();
for (String packageName : locationPackageBlacklist) {
ipw.print("[deny] ");
ipw.println(packageName);
}
List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
userId);
for (String packageName : locationPackageWhitelist) {
ipw.print("[allow] ");
ipw.println(packageName);
}
ipw.decreaseIndent();
}
} else {
List<String> locationPackageBlacklist = mLocationPackageBlacklist.getValueForUser(
userIds[0]);
for (String packageName : locationPackageBlacklist) {
ipw.print("[deny] ");
ipw.println(packageName);
}
List<String> locationPackageWhitelist = mLocationPackageWhitelist.getValueForUser(
userIds[0]);
for (String packageName : locationPackageWhitelist) {
ipw.print("[allow] ");
ipw.println(packageName);
}
}
ipw.decreaseIndent();
Set<String> backgroundThrottlePackageWhitelist =
mBackgroundThrottlePackageWhitelist.getValue();
if (!backgroundThrottlePackageWhitelist.isEmpty()) {
ipw.println("Throttling Allow Packages:");
ipw.increaseIndent();
for (String packageName : backgroundThrottlePackageWhitelist) {
ipw.println(packageName);
}
ipw.decreaseIndent();
}
PackageTagsList ignoreSettingsAllowlist = mIgnoreSettingsPackageAllowlist.getValue();
if (!ignoreSettingsAllowlist.isEmpty()) {
ipw.println("Bypass Allow Packages:");
ipw.increaseIndent();
ignoreSettingsAllowlist.dump(ipw);
ipw.decreaseIndent();
}
}
private abstract static class ObservingSetting extends ContentObserver {
private final CopyOnWriteArrayList<UserSettingChangedListener> mListeners;
@GuardedBy("this")
private boolean mRegistered;
ObservingSetting(Handler handler) {
super(handler);
mListeners = new CopyOnWriteArrayList<>();
}
protected synchronized boolean isRegistered() {
return mRegistered;
}
protected synchronized void register(Context context, Uri uri) {
if (mRegistered) {
return;
}
context.getContentResolver().registerContentObserver(
uri, false, this, UserHandle.USER_ALL);
mRegistered = true;
}
public void addListener(UserSettingChangedListener listener) {
mListeners.add(listener);
}
public void removeListener(UserSettingChangedListener listener) {
mListeners.remove(listener);
}
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
if (D) {
Log.d(TAG, "location setting changed [u" + userId + "]: " + uri);
}
for (UserSettingChangedListener listener : mListeners) {
listener.onSettingChanged(userId);
}
}
}
private static class IntegerSecureSetting extends ObservingSetting {
private final Context mContext;
private final String mSettingName;
IntegerSecureSetting(Context context, String settingName, Handler handler) {
super(handler);
mContext = context;
mSettingName = settingName;
}
void register() {
register(mContext, Settings.Secure.getUriFor(mSettingName));
}
public int getValueForUser(int defaultValue, int userId) {
final long identity = Binder.clearCallingIdentity();
try {
return Settings.Secure.getIntForUser(mContext.getContentResolver(), mSettingName,
defaultValue, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
private static class StringListCachedSecureSetting extends ObservingSetting {
private final Context mContext;
private final String mSettingName;
@GuardedBy("this")
private int mCachedUserId;
@GuardedBy("this")
private List<String> mCachedValue;
StringListCachedSecureSetting(Context context, String settingName,
Handler handler) {
super(handler);
mContext = context;
mSettingName = settingName;
mCachedUserId = UserHandle.USER_NULL;
}
public void register() {
register(mContext, Settings.Secure.getUriFor(mSettingName));
}
public synchronized List<String> getValueForUser(int userId) {
Preconditions.checkArgument(userId != UserHandle.USER_NULL);
List<String> value = mCachedValue;
if (userId != mCachedUserId) {
final long identity = Binder.clearCallingIdentity();
try {
String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
mSettingName, userId);
if (TextUtils.isEmpty(setting)) {
value = Collections.emptyList();
} else {
value = Arrays.asList(setting.split(","));
}
} finally {
Binder.restoreCallingIdentity(identity);
}
if (isRegistered()) {
mCachedUserId = userId;
mCachedValue = value;
}
}
return value;
}
public synchronized void invalidateForUser(int userId) {
if (mCachedUserId == userId) {
mCachedUserId = UserHandle.USER_NULL;
mCachedValue = null;
}
}
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
invalidateForUser(userId);
super.onChange(selfChange, uri, userId);
}
}
private static class BooleanGlobalSetting extends ObservingSetting {
private final Context mContext;
private final String mSettingName;
BooleanGlobalSetting(Context context, String settingName, Handler handler) {
super(handler);
mContext = context;
mSettingName = settingName;
}
public void register() {
register(mContext, Settings.Global.getUriFor(mSettingName));
}
public boolean getValue(boolean defaultValue) {
final long identity = Binder.clearCallingIdentity();
try {
return Settings.Global.getInt(mContext.getContentResolver(), mSettingName,
defaultValue ? 1 : 0) != 0;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
private static class LongGlobalSetting extends ObservingSetting {
private final Context mContext;
private final String mSettingName;
LongGlobalSetting(Context context, String settingName, Handler handler) {
super(handler);
mContext = context;
mSettingName = settingName;
}
public void register() {
register(mContext, Settings.Global.getUriFor(mSettingName));
}
public long getValue(long defaultValue) {
final long identity = Binder.clearCallingIdentity();
try {
return Settings.Global.getLong(mContext.getContentResolver(), mSettingName,
defaultValue);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
private static class StringSetCachedGlobalSetting extends ObservingSetting {
private final Context mContext;
private final String mSettingName;
private final Supplier<ArraySet<String>> mBaseValuesSupplier;
@GuardedBy("this")
private boolean mValid;
@GuardedBy("this")
private ArraySet<String> mCachedValue;
StringSetCachedGlobalSetting(Context context, String settingName,
Supplier<ArraySet<String>> baseValuesSupplier, Handler handler) {
super(handler);
mContext = context;
mSettingName = settingName;
mBaseValuesSupplier = baseValuesSupplier;
mValid = false;
}
public void register() {
register(mContext, Settings.Global.getUriFor(mSettingName));
}
public synchronized Set<String> getValue() {
ArraySet<String> value = mCachedValue;
if (!mValid) {
final long identity = Binder.clearCallingIdentity();
try {
value = new ArraySet<>(mBaseValuesSupplier.get());
String setting = Settings.Global.getString(mContext.getContentResolver(),
mSettingName);
if (!TextUtils.isEmpty(setting)) {
value.addAll(Arrays.asList(setting.split(",")));
}
} finally {
Binder.restoreCallingIdentity(identity);
}
if (isRegistered()) {
mValid = true;
mCachedValue = value;
}
}
return value;
}
public synchronized void invalidate() {
mValid = false;
mCachedValue = null;
}
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
invalidate();
super.onChange(selfChange, uri, userId);
}
}
private static class DeviceConfigSetting implements DeviceConfig.OnPropertiesChangedListener {
protected final String mName;
private final CopyOnWriteArrayList<GlobalSettingChangedListener> mListeners;
@GuardedBy("this")
private boolean mRegistered;
DeviceConfigSetting(String name) {
mName = name;
mListeners = new CopyOnWriteArrayList<>();
}
protected synchronized boolean isRegistered() {
return mRegistered;
}
protected synchronized void register() {
if (mRegistered) {
return;
}
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LOCATION,
FgThread.getExecutor(), this);
mRegistered = true;
}
public void addListener(GlobalSettingChangedListener listener) {
mListeners.add(listener);
}
public void removeListener(GlobalSettingChangedListener listener) {
mListeners.remove(listener);
}
@Override
public final void onPropertiesChanged(DeviceConfig.Properties properties) {
if (!properties.getKeyset().contains(mName)) {
return;
}
onPropertiesChanged();
}
public void onPropertiesChanged() {
if (D) {
Log.d(TAG, "location device config setting changed: " + mName);
}
for (UserSettingChangedListener listener : mListeners) {
listener.onSettingChanged(UserHandle.USER_ALL);
}
}
}
private static class PackageTagsListSetting extends DeviceConfigSetting {
private final Supplier<ArrayMap<String, ArraySet<String>>> mBaseValuesSupplier;
@GuardedBy("this")
private boolean mValid;
@GuardedBy("this")
private PackageTagsList mCachedValue;
PackageTagsListSetting(String name,
Supplier<ArrayMap<String, ArraySet<String>>> baseValuesSupplier) {
super(name);
mBaseValuesSupplier = baseValuesSupplier;
}
public synchronized PackageTagsList getValue() {
PackageTagsList value = mCachedValue;
if (!mValid) {
final long identity = Binder.clearCallingIdentity();
try {
PackageTagsList.Builder builder = new PackageTagsList.Builder().add(
mBaseValuesSupplier.get());
String setting = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_LOCATION,
mName);
if (!TextUtils.isEmpty(setting)) {
for (String packageAndTags : setting.split(",")) {
if (TextUtils.isEmpty(packageAndTags)) {
continue;
}
String[] packageThenTags = packageAndTags.split(";");
String packageName = packageThenTags[0];
if (packageThenTags.length == 1) {
builder.add(packageName);
} else {
for (int i = 1; i < packageThenTags.length; i++) {
String attributionTag = packageThenTags[i];
if ("null".equals(attributionTag)) {
attributionTag = null;
}
if ("*".equals(attributionTag)) {
builder.add(packageName);
} else {
builder.add(packageName, attributionTag);
}
}
}
}
}
value = builder.build();
} finally {
Binder.restoreCallingIdentity(identity);
}
if (isRegistered()) {
mValid = true;
mCachedValue = value;
}
}
return value;
}
public synchronized void invalidate() {
mValid = false;
mCachedValue = null;
}
@Override
public void onPropertiesChanged() {
invalidate();
super.onPropertiesChanged();
}
}
}