Add ADAS GNSS bypass functionality

ADAS applications on automotive device require a way to access GNSS
sensors even when the master location toggle is off, so that they can
provide automotive safety services.

Bug: 156688086
Test: atest LocationProviderManagerTest
Change-Id: I438a781b2202c488da97f7ea732f87403d1068e4
This commit is contained in:
Soonil Nagarkar
2021-06-16 15:03:05 -07:00
parent d616f1ed95
commit e58fea5a45
25 changed files with 1378 additions and 92 deletions

View File

@@ -1706,6 +1706,10 @@
config_enableFusedLocationOverlay is false. -->
<string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
<!-- Default value for the ADAS GNSS Location Enabled setting if this setting has never been
set before. -->
<bool name="config_defaultAdasGnssLocationEnabled" translatable="false">false</bool>
<string-array name="config_locationExtraPackageNames" translatable="false"></string-array>
<!-- The package name of the default network recommendation app.

View File

@@ -1918,6 +1918,7 @@
<java-symbol type="bool" name="config_tintNotificationActionButtons" />
<java-symbol type="bool" name="config_dozeAfterScreenOffByDefault" />
<java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
<java-symbol type="bool" name="config_defaultAdasGnssLocationEnabled" />
<java-symbol type="bool" name="config_enableFusedLocationOverlay" />
<java-symbol type="bool" name="config_enableGeocoderOverlay" />
<java-symbol type="bool" name="config_enableGeofenceOverlay" />

View File

@@ -122,6 +122,9 @@ interface ILocationManager
boolean isLocationEnabledForUser(int userId);
void setLocationEnabledForUser(boolean enabled, int userId);
boolean isAdasGnssLocationEnabledForUser(int userId);
void setAdasGnssLocationEnabledForUser(boolean enabled, int userId);
void addTestProvider(String name, in ProviderProperties properties,
in List<String> locationTags, String packageName, @nullable String attributionTag);
void removeTestProvider(String provider, String packageName, @nullable String attributionTag);

View File

@@ -34,12 +34,15 @@ import java.util.Objects;
public final class LastLocationRequest implements Parcelable {
private final boolean mHiddenFromAppOps;
private final boolean mAdasGnssBypass;
private final boolean mLocationSettingsIgnored;
private LastLocationRequest(
boolean hiddenFromAppOps,
boolean adasGnssBypass,
boolean locationSettingsIgnored) {
mHiddenFromAppOps = hiddenFromAppOps;
mAdasGnssBypass = adasGnssBypass;
mLocationSettingsIgnored = locationSettingsIgnored;
}
@@ -55,6 +58,21 @@ public final class LastLocationRequest implements Parcelable {
return mHiddenFromAppOps;
}
/**
* Returns true if this request may access GNSS even if location settings would normally deny
* this, in order to enable automotive safety features. This field is only respected on
* automotive devices, and only if the client is recognized as a legitimate ADAS (Advanced
* Driving Assistance Systems) application.
*
* @return true if all limiting factors will be ignored to satisfy GNSS request
* @hide
*/
// TODO: make this system api
public boolean isAdasGnssBypass() {
return mAdasGnssBypass;
}
/**
* Returns true if location settings, throttling, background location limits, and any other
* possible limiting factors will be ignored in order to satisfy this last location request.
@@ -65,12 +83,22 @@ public final class LastLocationRequest implements Parcelable {
return mLocationSettingsIgnored;
}
/**
* Returns true if any bypass flag is set on this request. For internal use only.
*
* @hide
*/
public boolean isBypass() {
return mAdasGnssBypass || mLocationSettingsIgnored;
}
public static final @NonNull Parcelable.Creator<LastLocationRequest> CREATOR =
new Parcelable.Creator<LastLocationRequest>() {
@Override
public LastLocationRequest createFromParcel(Parcel in) {
return new LastLocationRequest(
/* hiddenFromAppOps= */ in.readBoolean(),
/* adasGnssBypass= */ in.readBoolean(),
/* locationSettingsIgnored= */ in.readBoolean());
}
@Override
@@ -86,6 +114,7 @@ public final class LastLocationRequest implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeBoolean(mHiddenFromAppOps);
parcel.writeBoolean(mAdasGnssBypass);
parcel.writeBoolean(mLocationSettingsIgnored);
}
@@ -99,12 +128,13 @@ public final class LastLocationRequest implements Parcelable {
}
LastLocationRequest that = (LastLocationRequest) o;
return mHiddenFromAppOps == that.mHiddenFromAppOps
&& mAdasGnssBypass == that.mAdasGnssBypass
&& mLocationSettingsIgnored == that.mLocationSettingsIgnored;
}
@Override
public int hashCode() {
return Objects.hash(mHiddenFromAppOps, mLocationSettingsIgnored);
return Objects.hash(mHiddenFromAppOps, mAdasGnssBypass, mLocationSettingsIgnored);
}
@NonNull
@@ -115,8 +145,11 @@ public final class LastLocationRequest implements Parcelable {
if (mHiddenFromAppOps) {
s.append("hiddenFromAppOps, ");
}
if (mAdasGnssBypass) {
s.append("adasGnssBypass, ");
}
if (mLocationSettingsIgnored) {
s.append("locationSettingsIgnored, ");
s.append("settingsBypass, ");
}
if (s.length() > "LastLocationRequest[".length()) {
s.setLength(s.length() - 2);
@@ -131,6 +164,7 @@ public final class LastLocationRequest implements Parcelable {
public static final class Builder {
private boolean mHiddenFromAppOps;
private boolean mAdasGnssBypass;
private boolean mLocationSettingsIgnored;
/**
@@ -138,6 +172,7 @@ public final class LastLocationRequest implements Parcelable {
*/
public Builder() {
mHiddenFromAppOps = false;
mAdasGnssBypass = false;
mLocationSettingsIgnored = false;
}
@@ -146,6 +181,7 @@ public final class LastLocationRequest implements Parcelable {
*/
public Builder(@NonNull LastLocationRequest lastLocationRequest) {
mHiddenFromAppOps = lastLocationRequest.mHiddenFromAppOps;
mAdasGnssBypass = lastLocationRequest.mAdasGnssBypass;
mLocationSettingsIgnored = lastLocationRequest.mLocationSettingsIgnored;
}
@@ -163,6 +199,25 @@ public final class LastLocationRequest implements Parcelable {
return this;
}
/**
* If set to true, indicates that the client is an ADAS (Advanced Driving Assistance
* Systems) client, which requires access to GNSS even if location settings would normally
* deny this, in order to enable auto safety features. This field is only respected on
* automotive devices, and only if the client is recognized as a legitimate ADAS
* application. Defaults to false.
*
* <p>Permissions enforcement occurs when resulting location request is actually used, not
* when this method is invoked.
*
* @hide
*/
// TODO: make this system api
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public @NonNull LastLocationRequest.Builder setAdasGnssBypass(boolean adasGnssBypass) {
mAdasGnssBypass = adasGnssBypass;
return this;
}
/**
* If set to true, indicates that location settings, throttling, background location limits,
* and any other possible limiting factors should be ignored in order to satisfy this
@@ -186,6 +241,7 @@ public final class LastLocationRequest implements Parcelable {
public @NonNull LastLocationRequest build() {
return new LastLocationRequest(
mHiddenFromAppOps,
mAdasGnssBypass,
mLocationSettingsIgnored);
}
}

View File

@@ -314,6 +314,33 @@ public class LocationManager {
*/
public static final String EXTRA_LOCATION_ENABLED = "android.location.extra.LOCATION_ENABLED";
/**
* Broadcast intent action when the ADAS (Advanced Driving Assistance Systems) GNSS location
* enabled state changes. Includes a boolean intent extra, {@link #EXTRA_ADAS_GNSS_ENABLED},
* with the enabled state of ADAS GNSS location. This broadcast only has meaning on automotive
* devices.
*
* @see #EXTRA_ADAS_GNSS_ENABLED
* @see #isAdasGnssLocationEnabled()
*
* @hide
*/
// TODO: @SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ADAS_GNSS_ENABLED_CHANGED =
"android.location.action.ADAS_GNSS_ENABLED_CHANGED";
/**
* Intent extra included with {@link #ACTION_ADAS_GNSS_ENABLED_CHANGED} broadcasts, containing
* the boolean enabled state of ADAS GNSS location.
*
* @see #ACTION_ADAS_GNSS_ENABLED_CHANGED
*
* @hide
*/
// TODO: @SystemApi
public static final String EXTRA_ADAS_GNSS_ENABLED = "android.location.extra.ADAS_GNSS_ENABLED";
/**
* Broadcast intent action indicating that a high power location requests
* has either started or stopped being active. The current state of
@@ -620,6 +647,42 @@ public class LocationManager {
}
}
/**
* Returns the current enabled/disabled state of ADAS (Advanced Driving Assistance Systems)
* GNSS location access for the given user. This controls safety critical automotive access to
* GNSS location. This only has meaning on automotive devices.
*
* @return true if ADAS location is enabled and false if ADAS location is disabled.
*
* @hide
*/
//TODO: @SystemApi
public boolean isAdasGnssLocationEnabled() {
try {
return mService.isAdasGnssLocationEnabledForUser(mContext.getUser().getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Enables or disables ADAS (Advanced Driving Assistance Systems) GNSS location access for the
* given user. This only has meaning on automotive devices.
*
* @param enabled true to enable ADAS location and false to disable ADAS location.
*
* @hide
*/
// TODO: @SystemApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public void setAdasGnssLocationEnabled(boolean enabled) {
try {
mService.setAdasGnssLocationEnabledForUser(enabled, mContext.getUser().getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Returns the current enabled/disabled status of the given provider. To listen for changes, see
* {@link #PROVIDERS_CHANGED_ACTION}.

View File

@@ -194,6 +194,7 @@ public final class LocationRequest implements Parcelable {
private float mMinUpdateDistanceMeters;
private final long mMaxUpdateDelayMillis;
private boolean mHideFromAppOps;
private final boolean mAdasGnssBypass;
private boolean mLocationSettingsIgnored;
private boolean mLowPower;
private @Nullable WorkSource mWorkSource;
@@ -236,7 +237,7 @@ public final class LocationRequest implements Parcelable {
if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
quality = POWER_NONE;
} else if (LocationManager.GPS_PROVIDER.equals(provider)) {
quality = ACCURACY_FINE;
quality = QUALITY_HIGH_ACCURACY;
} else {
quality = POWER_LOW;
}
@@ -289,6 +290,7 @@ public final class LocationRequest implements Parcelable {
float minUpdateDistanceMeters,
long maxUpdateDelayMillis,
boolean hiddenFromAppOps,
boolean adasGnssBypass,
boolean locationSettingsIgnored,
boolean lowPower,
WorkSource workSource) {
@@ -302,8 +304,9 @@ public final class LocationRequest implements Parcelable {
mMinUpdateDistanceMeters = minUpdateDistanceMeters;
mMaxUpdateDelayMillis = maxUpdateDelayMillis;
mHideFromAppOps = hiddenFromAppOps;
mLowPower = lowPower;
mAdasGnssBypass = adasGnssBypass;
mLocationSettingsIgnored = locationSettingsIgnored;
mLowPower = lowPower;
mWorkSource = Objects.requireNonNull(workSource);
}
@@ -339,15 +342,15 @@ public final class LocationRequest implements Parcelable {
switch (quality) {
case POWER_HIGH:
// fall through
case ACCURACY_FINE:
case QUALITY_HIGH_ACCURACY:
mQuality = QUALITY_HIGH_ACCURACY;
break;
case ACCURACY_BLOCK:
case QUALITY_BALANCED_POWER_ACCURACY:
mQuality = QUALITY_BALANCED_POWER_ACCURACY;
break;
case POWER_LOW:
// fall through
case ACCURACY_CITY:
case QUALITY_LOW_POWER:
mQuality = QUALITY_LOW_POWER;
break;
case POWER_NONE:
@@ -647,6 +650,21 @@ public final class LocationRequest implements Parcelable {
return mHideFromAppOps;
}
/**
* Returns true if this request may access GNSS even if location settings would normally deny
* this, in order to enable automotive safety features. This field is only respected on
* automotive devices, and only if the client is recognized as a legitimate ADAS (Advanced
* Driving Assistance Systems) application.
*
* @return true if all limiting factors will be ignored to satisfy GNSS request
*
* @hide
*/
// TODO: @SystemApi
public boolean isAdasGnssBypass() {
return mAdasGnssBypass;
}
/**
* @hide
* @deprecated LocationRequests should be treated as immutable.
@@ -672,6 +690,15 @@ public final class LocationRequest implements Parcelable {
return mLocationSettingsIgnored;
}
/**
* Returns true if any bypass flag is set on this request. For internal use only.
*
* @hide
*/
public boolean isBypass() {
return mAdasGnssBypass || mLocationSettingsIgnored;
}
/**
* @hide
* @deprecated LocationRequests should be treated as immutable.
@@ -749,6 +776,7 @@ public final class LocationRequest implements Parcelable {
/* minUpdateDistanceMeters= */ in.readFloat(),
/* maxUpdateDelayMillis= */ in.readLong(),
/* hiddenFromAppOps= */ in.readBoolean(),
/* adasGnssBypass= */ in.readBoolean(),
/* locationSettingsIgnored= */ in.readBoolean(),
/* lowPower= */ in.readBoolean(),
/* workSource= */ in.readTypedObject(WorkSource.CREATOR));
@@ -777,6 +805,7 @@ public final class LocationRequest implements Parcelable {
parcel.writeFloat(mMinUpdateDistanceMeters);
parcel.writeLong(mMaxUpdateDelayMillis);
parcel.writeBoolean(mHideFromAppOps);
parcel.writeBoolean(mAdasGnssBypass);
parcel.writeBoolean(mLocationSettingsIgnored);
parcel.writeBoolean(mLowPower);
parcel.writeTypedObject(mWorkSource, 0);
@@ -801,6 +830,7 @@ public final class LocationRequest implements Parcelable {
&& Float.compare(that.mMinUpdateDistanceMeters, mMinUpdateDistanceMeters) == 0
&& mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis
&& mHideFromAppOps == that.mHideFromAppOps
&& mAdasGnssBypass == that.mAdasGnssBypass
&& mLocationSettingsIgnored == that.mLocationSettingsIgnored
&& mLowPower == that.mLowPower
&& Objects.equals(mProvider, that.mProvider)
@@ -866,8 +896,11 @@ public final class LocationRequest implements Parcelable {
if (mHideFromAppOps) {
s.append(", hiddenFromAppOps");
}
if (mAdasGnssBypass) {
s.append(", adasGnssBypass");
}
if (mLocationSettingsIgnored) {
s.append(", locationSettingsIgnored");
s.append(", settingsBypass");
}
if (mWorkSource != null && !mWorkSource.isEmpty()) {
s.append(", ").append(mWorkSource);
@@ -889,6 +922,7 @@ public final class LocationRequest implements Parcelable {
private float mMinUpdateDistanceMeters;
private long mMaxUpdateDelayMillis;
private boolean mHiddenFromAppOps;
private boolean mAdasGnssBypass;
private boolean mLocationSettingsIgnored;
private boolean mLowPower;
@Nullable private WorkSource mWorkSource;
@@ -908,6 +942,7 @@ public final class LocationRequest implements Parcelable {
mMinUpdateDistanceMeters = 0;
mMaxUpdateDelayMillis = 0;
mHiddenFromAppOps = false;
mAdasGnssBypass = false;
mLocationSettingsIgnored = false;
mLowPower = false;
mWorkSource = null;
@@ -925,6 +960,7 @@ public final class LocationRequest implements Parcelable {
mMinUpdateDistanceMeters = locationRequest.mMinUpdateDistanceMeters;
mMaxUpdateDelayMillis = locationRequest.mMaxUpdateDelayMillis;
mHiddenFromAppOps = locationRequest.mHideFromAppOps;
mAdasGnssBypass = locationRequest.mAdasGnssBypass;
mLocationSettingsIgnored = locationRequest.mLocationSettingsIgnored;
mLowPower = locationRequest.mLowPower;
mWorkSource = locationRequest.mWorkSource;
@@ -977,10 +1013,10 @@ public final class LocationRequest implements Parcelable {
public @NonNull Builder setQuality(@NonNull Criteria criteria) {
switch (criteria.getAccuracy()) {
case Criteria.ACCURACY_COARSE:
mQuality = ACCURACY_BLOCK;
mQuality = QUALITY_BALANCED_POWER_ACCURACY;
break;
case Criteria.ACCURACY_FINE:
mQuality = ACCURACY_FINE;
mQuality = QUALITY_HIGH_ACCURACY;
break;
default: {
if (criteria.getPowerRequirement() == Criteria.POWER_HIGH) {
@@ -1091,6 +1127,25 @@ public final class LocationRequest implements Parcelable {
return this;
}
/**
* If set to true, indicates that the client is an ADAS (Advanced Driving Assistance
* Systems) client, which requires access to GNSS even if location settings would normally
* deny this, in order to enable auto safety features. This field is only respected on
* automotive devices, and only if the client is recognized as a legitimate ADAS
* application. Defaults to false.
*
* <p>Permissions enforcement occurs when resulting location request is actually used, not
* when this method is invoked.
*
* @hide
*/
// TODO: @SystemApi
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
public @NonNull Builder setAdasGnssBypass(boolean adasGnssBypass) {
mAdasGnssBypass = adasGnssBypass;
return this;
}
/**
* If set to true, indicates that location settings, throttling, background location limits,
* and any other possible limiting factors should be ignored in order to satisfy this
@@ -1171,6 +1226,7 @@ public final class LocationRequest implements Parcelable {
mMinUpdateDistanceMeters,
mMaxUpdateDelayMillis,
mHiddenFromAppOps,
mAdasGnssBypass,
mLocationSettingsIgnored,
mLowPower,
new WorkSource(mWorkSource));

View File

@@ -44,12 +44,19 @@ public final class ProviderRequest implements Parcelable {
public static final long INTERVAL_DISABLED = Long.MAX_VALUE;
public static final @NonNull ProviderRequest EMPTY_REQUEST = new ProviderRequest(
INTERVAL_DISABLED, QUALITY_BALANCED_POWER_ACCURACY, 0, false, false, new WorkSource());
INTERVAL_DISABLED,
QUALITY_BALANCED_POWER_ACCURACY,
0,
false,
false,
false,
new WorkSource());
private final long mIntervalMillis;
private final @Quality int mQuality;
private final long mMaxUpdateDelayMillis;
private final boolean mLowPower;
private final boolean mAdasGnssBypass;
private final boolean mLocationSettingsIgnored;
private final WorkSource mWorkSource;
@@ -72,12 +79,14 @@ public final class ProviderRequest implements Parcelable {
@Quality int quality,
long maxUpdateDelayMillis,
boolean lowPower,
boolean adasGnssBypass,
boolean locationSettingsIgnored,
@NonNull WorkSource workSource) {
mIntervalMillis = intervalMillis;
mQuality = quality;
mMaxUpdateDelayMillis = maxUpdateDelayMillis;
mLowPower = lowPower;
mAdasGnssBypass = adasGnssBypass;
mLocationSettingsIgnored = locationSettingsIgnored;
mWorkSource = Objects.requireNonNull(workSource);
}
@@ -125,6 +134,18 @@ public final class ProviderRequest implements Parcelable {
return mLowPower;
}
/**
* Returns true if this request may access GNSS even if location settings would normally deny
* this, in order to enable automotive safety features. This field is only respected on
* automotive devices, and only if the client is recognized as a legitimate ADAS (Advanced
* Driving Assistance Systems) application.
*
* @hide
*/
public boolean isAdasGnssBypass() {
return mAdasGnssBypass;
}
/**
* Whether the provider should ignore all location settings, user consents, power restrictions
* or any other restricting factors and always satisfy this request to the best of their
@@ -134,6 +155,15 @@ public final class ProviderRequest implements Parcelable {
return mLocationSettingsIgnored;
}
/**
* Returns true if any bypass flag is set on this request.
*
* @hide
*/
public boolean isBypass() {
return mAdasGnssBypass || mLocationSettingsIgnored;
}
/**
* The power blame for this provider request.
*/
@@ -153,6 +183,7 @@ public final class ProviderRequest implements Parcelable {
/* quality= */ in.readInt(),
/* maxUpdateDelayMillis= */ in.readLong(),
/* lowPower= */ in.readBoolean(),
/* adasGnssBypass= */ in.readBoolean(),
/* locationSettingsIgnored= */ in.readBoolean(),
/* workSource= */ in.readTypedObject(WorkSource.CREATOR));
}
@@ -176,6 +207,7 @@ public final class ProviderRequest implements Parcelable {
parcel.writeInt(mQuality);
parcel.writeLong(mMaxUpdateDelayMillis);
parcel.writeBoolean(mLowPower);
parcel.writeBoolean(mAdasGnssBypass);
parcel.writeBoolean(mLocationSettingsIgnored);
parcel.writeTypedObject(mWorkSource, flags);
}
@@ -198,6 +230,7 @@ public final class ProviderRequest implements Parcelable {
&& mQuality == that.mQuality
&& mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis
&& mLowPower == that.mLowPower
&& mAdasGnssBypass == that.mAdasGnssBypass
&& mLocationSettingsIgnored == that.mLocationSettingsIgnored
&& mWorkSource.equals(that.mWorkSource);
}
@@ -229,8 +262,11 @@ public final class ProviderRequest implements Parcelable {
if (mLowPower) {
s.append(", lowPower");
}
if (mAdasGnssBypass) {
s.append(", adasGnssBypass");
}
if (mLocationSettingsIgnored) {
s.append(", locationSettingsIgnored");
s.append(", settingsBypass");
}
if (!mWorkSource.isEmpty()) {
s.append(", ").append(mWorkSource);
@@ -246,10 +282,12 @@ public final class ProviderRequest implements Parcelable {
* A Builder for {@link ProviderRequest}s.
*/
public static final class Builder {
private long mIntervalMillis = INTERVAL_DISABLED;
private int mQuality = QUALITY_BALANCED_POWER_ACCURACY;
private long mMaxUpdateDelayMillis = 0;
private boolean mLowPower;
private boolean mAdasGnssBypass;
private boolean mLocationSettingsIgnored;
private WorkSource mWorkSource = new WorkSource();
@@ -298,6 +336,16 @@ public final class ProviderRequest implements Parcelable {
return this;
}
/**
* Sets whether this ADAS request should bypass GNSS settings. False by default.
*
* @hide
*/
public @NonNull Builder setAdasGnssBypass(boolean adasGnssBypass) {
this.mAdasGnssBypass = adasGnssBypass;
return this;
}
/**
* Sets whether location settings should be ignored. False by default.
*/
@@ -326,6 +374,7 @@ public final class ProviderRequest implements Parcelable {
mQuality,
mMaxUpdateDelayMillis,
mLowPower,
mAdasGnssBypass,
mLocationSettingsIgnored,
mWorkSource);
}

View File

@@ -45,6 +45,7 @@ import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
@@ -91,6 +92,7 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
@@ -134,6 +136,8 @@ import com.android.server.location.provider.PassiveLocationProvider;
import com.android.server.location.provider.PassiveLocationProviderManager;
import com.android.server.location.provider.StationaryThrottlingLocationProvider;
import com.android.server.location.provider.proxy.ProxyLocationProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import java.io.FileDescriptor;
@@ -270,6 +274,8 @@ public class LocationManagerService extends ILocationManager.Stub implements
mGeofenceManager = new GeofenceManager(mContext, injector);
mInjector.getLocationSettings().registerLocationUserSettingsListener(
this::onLocationUserSettingsChanged);
mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
this::onLocationModeChanged);
mInjector.getSettingsHelper().addIgnoreSettingsAllowlistChangedListener(
@@ -476,6 +482,25 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
}
private void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
if (oldSettings.isAdasGnssLocationEnabled() != newSettings.isAdasGnssLocationEnabled()) {
boolean enabled = newSettings.isAdasGnssLocationEnabled();
if (D) {
Log.d(TAG, "[u" + userId + "] adas gnss location enabled = " + enabled);
}
EVENT_LOG.logAdasLocationEnabled(userId, enabled);
Intent intent = new Intent(LocationManager.ACTION_ADAS_GNSS_ENABLED_CHANGED)
.putExtra(LocationManager.EXTRA_ADAS_GNSS_ENABLED, enabled)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
}
private void onLocationModeChanged(int userId) {
boolean enabled = mInjector.getSettingsHelper().isLocationEnabled(userId);
LocationManager.invalidateLocalLocationEnabledCaches();
@@ -661,7 +686,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
// clients in the system process must have an attribution tag set
Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null);
request = validateLocationRequest(request, identity);
request = validateLocationRequest(provider, request, identity);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -687,7 +712,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
new IllegalArgumentException());
}
request = validateLocationRequest(request, identity);
request = validateLocationRequest(provider, request, identity);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -725,7 +750,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
}
request = validateLocationRequest(request, identity);
request = validateLocationRequest(provider, request, identity);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -734,33 +759,27 @@ public class LocationManagerService extends ILocationManager.Stub implements
manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent);
}
private LocationRequest validateLocationRequest(LocationRequest request,
private LocationRequest validateLocationRequest(String provider, LocationRequest request,
CallerIdentity identity) {
// validate unsanitized request
if (!request.getWorkSource().isEmpty()) {
mContext.enforceCallingOrSelfPermission(
permission.UPDATE_DEVICE_STATS,
"setting a work source requires " + permission.UPDATE_DEVICE_STATS);
}
if (request.isHiddenFromAppOps()) {
mContext.enforceCallingOrSelfPermission(
permission.UPDATE_APP_OPS_STATS,
"hiding from app ops requires " + permission.UPDATE_APP_OPS_STATS);
}
if (request.isLocationSettingsIgnored()) {
mContext.enforceCallingOrSelfPermission(
permission.WRITE_SECURE_SETTINGS,
"ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
}
// sanitize request
LocationRequest.Builder sanitized = new LocationRequest.Builder(request);
if (CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, Binder.getCallingUid())) {
if (request.isLowPower()) {
mContext.enforceCallingOrSelfPermission(
permission.LOCATION_HARDWARE,
"low power request requires " + permission.LOCATION_HARDWARE);
}
} else {
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
&& GPS_PROVIDER.equals(provider)
&& ArrayUtils.contains(mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationDriverAssistancePackageNames),
identity.getPackageName())) {
sanitized.setAdasGnssBypass(true);
}
if (!CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, Binder.getCallingUid())) {
if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
!= PERMISSION_GRANTED) {
sanitized.setLowPower(false);
@@ -786,7 +805,52 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
sanitized.setWorkSource(workSource);
return sanitized.build();
request = sanitized.build();
// validate sanitized request
boolean isLocationProvider = mLocalService.isProvider(null, identity);
if (request.isLowPower() && CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS,
identity.getUid())) {
mContext.enforceCallingOrSelfPermission(
permission.LOCATION_HARDWARE,
"low power request requires " + permission.LOCATION_HARDWARE);
}
if (request.isHiddenFromAppOps()) {
mContext.enforceCallingOrSelfPermission(
permission.UPDATE_APP_OPS_STATS,
"hiding from app ops requires " + permission.UPDATE_APP_OPS_STATS);
}
if (request.isAdasGnssBypass()) {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalArgumentException(
"adas gnss bypass requests are only allowed on automotive devices");
}
if (!GPS_PROVIDER.equals(provider)) {
throw new IllegalArgumentException(
"adas gnss bypass requests are only allowed on the \"gps\" provider");
}
if (!ArrayUtils.contains(mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationDriverAssistancePackageNames),
identity.getPackageName())) {
throw new SecurityException(
"only verified adas packages may use adas gnss bypass requests");
}
if (!isLocationProvider) {
mContext.enforceCallingOrSelfPermission(
permission.WRITE_SECURE_SETTINGS,
"adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS);
}
}
if (request.isLocationSettingsIgnored()) {
if (!isLocationProvider) {
mContext.enforceCallingOrSelfPermission(
permission.WRITE_SECURE_SETTINGS,
"ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
}
}
return request;
}
@Override
@@ -834,7 +898,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
// clients in the system process must have an attribution tag set
Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
request = validateLastLocationRequest(request);
request = validateLastLocationRequest(provider, request, identity);
LocationProviderManager manager = getLocationProviderManager(provider);
if (manager == null) {
@@ -844,16 +908,58 @@ public class LocationManagerService extends ILocationManager.Stub implements
return manager.getLastLocation(request, identity, permissionLevel);
}
private LastLocationRequest validateLastLocationRequest(LastLocationRequest request) {
private LastLocationRequest validateLastLocationRequest(String provider,
LastLocationRequest request,
CallerIdentity identity) {
// sanitize request
LastLocationRequest.Builder sanitized = new LastLocationRequest.Builder(request);
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
&& GPS_PROVIDER.equals(provider)
&& ArrayUtils.contains(mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationDriverAssistancePackageNames),
identity.getPackageName())) {
sanitized.setAdasGnssBypass(true);
}
request = sanitized.build();
// validate request
boolean isLocationProvider = mLocalService.isProvider(null, identity);
if (request.isHiddenFromAppOps()) {
mContext.enforceCallingOrSelfPermission(
permission.UPDATE_APP_OPS_STATS,
"hiding from app ops requires " + permission.UPDATE_APP_OPS_STATS);
}
if (request.isAdasGnssBypass()) {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalArgumentException(
"adas gnss bypass requests are only allowed on automotive devices");
}
if (!GPS_PROVIDER.equals(provider)) {
throw new IllegalArgumentException(
"adas gnss bypass requests are only allowed on the \"gps\" provider");
}
if (!ArrayUtils.contains(mContext.getResources().getStringArray(
com.android.internal.R.array.config_locationDriverAssistancePackageNames),
identity.getPackageName())) {
throw new SecurityException(
"only verified adas packages may use adas gnss bypass requests");
}
if (!isLocationProvider) {
mContext.enforceCallingOrSelfPermission(
permission.WRITE_SECURE_SETTINGS,
"adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS);
}
}
if (request.isLocationSettingsIgnored()) {
mContext.enforceCallingOrSelfPermission(
permission.WRITE_SECURE_SETTINGS,
"ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
if (!isLocationProvider) {
mContext.enforceCallingOrSelfPermission(
permission.WRITE_SECURE_SETTINGS,
"ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS);
}
}
return request;
@@ -1125,6 +1231,24 @@ public class LocationManagerService extends ILocationManager.Stub implements
return mInjector.getSettingsHelper().isLocationEnabled(userId);
}
@Override
public void setAdasGnssLocationEnabledForUser(boolean enabled, int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "setAdasGnssLocationEnabledForUser", null);
mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null);
mInjector.getLocationSettings().updateUserSettings(userId,
settings -> settings.withAdasGnssLocationEnabled(enabled));
}
@Override
public boolean isAdasGnssLocationEnabledForUser(int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "isAdasGnssLocationEnabledForUser", null);
return mInjector.getLocationSettings().getUserSettings(userId).isAdasGnssLocationEnabled();
}
@Override
public boolean isProviderEnabledForUser(String provider, int userId) {
return mLocalService.isProviderEnabledForUser(provider, userId);
@@ -1555,11 +1679,12 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
}
private static class SystemInjector implements Injector {
private static final class SystemInjector implements Injector {
private final Context mContext;
private final UserInfoHelper mUserInfoHelper;
private final LocationSettings mLocationSettings;
private final AlarmHelper mAlarmHelper;
private final SystemAppOpsHelper mAppOpsHelper;
private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
@@ -1584,6 +1709,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
mContext = context;
mUserInfoHelper = userInfoHelper;
mLocationSettings = new LocationSettings(context);
mAlarmHelper = new SystemAlarmHelper(context);
mAppOpsHelper = new SystemAppOpsHelper(context);
mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
@@ -1620,6 +1746,11 @@ public class LocationManagerService extends ILocationManager.Stub implements
return mUserInfoHelper;
}
@Override
public LocationSettings getLocationSettings() {
return mLocationSettings;
}
@Override
public AlarmHelper getAlarmHelper() {
return mAlarmHelper;

View File

@@ -17,6 +17,7 @@
package com.android.server.location;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.provider.ProviderProperties;
@@ -60,6 +61,14 @@ class LocationShellCommand extends BasicShellCommandHandler {
handleSetLocationEnabled();
return 0;
}
case "is-adas-gnss-location-enabled": {
handleIsAdasGnssLocationEnabled();
return 0;
}
case "set-adas-gnss-location-enabled": {
handleSetAdasGnssLocationEnabled();
return 0;
}
case "providers": {
String command = getNextArgRequired();
return parseProvidersCommand(command);
@@ -134,6 +143,52 @@ class LocationShellCommand extends BasicShellCommandHandler {
mService.setLocationEnabledForUser(enabled, userId);
}
private void handleIsAdasGnssLocationEnabled() {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalStateException("command only recognized on automotive devices");
}
int userId = UserHandle.USER_CURRENT_OR_SELF;
do {
String option = getNextOption();
if (option == null) {
break;
}
if ("--user".equals(option)) {
userId = UserHandle.parseUserArg(getNextArgRequired());
} else {
throw new IllegalArgumentException("Unknown option: " + option);
}
} while (true);
getOutPrintWriter().println(mService.isAdasGnssLocationEnabledForUser(userId));
}
private void handleSetAdasGnssLocationEnabled() {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
throw new IllegalStateException("command only recognized on automotive devices");
}
boolean enabled = Boolean.parseBoolean(getNextArgRequired());
int userId = UserHandle.USER_CURRENT_OR_SELF;
do {
String option = getNextOption();
if (option == null) {
break;
}
if ("--user".equals(option)) {
userId = UserHandle.parseUserArg(getNextArgRequired());
} else {
throw new IllegalArgumentException("Unknown option: " + option);
}
} while (true);
mService.setAdasGnssLocationEnabledForUser(enabled, userId);
}
private void handleAddTestProvider() {
String provider = getNextArgRequired();
@@ -297,6 +352,14 @@ class LocationShellCommand extends BasicShellCommandHandler {
pw.println(" set-location-enabled true|false [--user <USER_ID>]");
pw.println(" Sets the master location switch enabled state. If no user is specified,");
pw.println(" the current user is assumed.");
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
pw.println(" is-adas-gnss-location-enabled [--user <USER_ID>]");
pw.println(" Gets the ADAS GNSS location enabled state. If no user is specified,");
pw.println(" the current user is assumed.");
pw.println(" set-adas-gnss-location-enabled true|false [--user <USER_ID>]");
pw.println(" Sets the ADAS GNSS location enabled state. If no user is specified,");
pw.println(" the current user is assumed.");
}
pw.println(" providers");
pw.println(" The providers command is followed by a subcommand, as listed below:");
pw.println();
@@ -323,9 +386,8 @@ class LocationShellCommand extends BasicShellCommandHandler {
pw.println(" Common commands that may be supported by the gps provider, depending on");
pw.println(" hardware and software configurations:");
pw.println(" delete_aiding_data - requests deletion of any predictive aiding data");
pw.println(" force_time_injection - requests NTP time injection to chipset");
pw.println(" force_psds_injection - "
+ "requests predictive aiding data injection to chipset");
pw.println(" request_power_stats - requests GNSS power stats update from chipset");
pw.println(" force_time_injection - requests NTP time injection");
pw.println(" force_psds_injection - requests predictive aiding data injection");
pw.println(" request_power_stats - requests GNSS power stats update");
}
}

View File

@@ -55,19 +55,20 @@ public class LocationEventLog extends LocalEventLog {
private static final int EVENT_USER_SWITCHED = 1;
private static final int EVENT_LOCATION_ENABLED = 2;
private static final int EVENT_PROVIDER_ENABLED = 3;
private static final int EVENT_PROVIDER_MOCKED = 4;
private static final int EVENT_PROVIDER_CLIENT_REGISTER = 5;
private static final int EVENT_PROVIDER_CLIENT_UNREGISTER = 6;
private static final int EVENT_PROVIDER_CLIENT_FOREGROUND = 7;
private static final int EVENT_PROVIDER_CLIENT_BACKGROUND = 8;
private static final int EVENT_PROVIDER_CLIENT_PERMITTED = 9;
private static final int EVENT_PROVIDER_CLIENT_UNPERMITTED = 10;
private static final int EVENT_PROVIDER_UPDATE_REQUEST = 11;
private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 12;
private static final int EVENT_PROVIDER_DELIVER_LOCATION = 13;
private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 14;
private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 15;
private static final int EVENT_ADAS_LOCATION_ENABLED = 3;
private static final int EVENT_PROVIDER_ENABLED = 4;
private static final int EVENT_PROVIDER_MOCKED = 5;
private static final int EVENT_PROVIDER_CLIENT_REGISTER = 6;
private static final int EVENT_PROVIDER_CLIENT_UNREGISTER = 7;
private static final int EVENT_PROVIDER_CLIENT_FOREGROUND = 8;
private static final int EVENT_PROVIDER_CLIENT_BACKGROUND = 9;
private static final int EVENT_PROVIDER_CLIENT_PERMITTED = 10;
private static final int EVENT_PROVIDER_CLIENT_UNPERMITTED = 11;
private static final int EVENT_PROVIDER_UPDATE_REQUEST = 12;
private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 13;
private static final int EVENT_PROVIDER_DELIVER_LOCATION = 14;
private static final int EVENT_PROVIDER_STATIONARY_THROTTLED = 15;
private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 16;
@GuardedBy("mAggregateStats")
private final ArrayMap<String, ArrayMap<CallerIdentity, AggregateStats>> mAggregateStats;
@@ -116,6 +117,11 @@ public class LocationEventLog extends LocalEventLog {
addLogEvent(EVENT_LOCATION_ENABLED, userId, enabled);
}
/** Logs a location enabled/disabled event. */
public void logAdasLocationEnabled(int userId, boolean enabled) {
addLogEvent(EVENT_ADAS_LOCATION_ENABLED, userId, enabled);
}
/** Logs a location provider enabled/disabled event. */
public void logProviderEnabled(String provider, int userId, boolean enabled) {
addLogEvent(EVENT_PROVIDER_ENABLED, provider, userId, enabled);
@@ -219,6 +225,9 @@ public class LocationEventLog extends LocalEventLog {
return new UserSwitchedEvent(timeDelta, (Integer) args[0], (Integer) args[1]);
case EVENT_LOCATION_ENABLED:
return new LocationEnabledEvent(timeDelta, (Integer) args[0], (Boolean) args[1]);
case EVENT_ADAS_LOCATION_ENABLED:
return new LocationAdasEnabledEvent(timeDelta, (Integer) args[0],
(Boolean) args[1]);
case EVENT_PROVIDER_ENABLED:
return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
(Boolean) args[2]);
@@ -517,6 +526,23 @@ public class LocationEventLog extends LocalEventLog {
}
}
private static final class LocationAdasEnabledEvent extends LogEvent {
private final int mUserId;
private final boolean mEnabled;
LocationAdasEnabledEvent(long timeDelta, int userId, boolean enabled) {
super(timeDelta);
mUserId = userId;
mEnabled = enabled;
}
@Override
public String getLogString() {
return "adas location [u" + mUserId + "] " + (mEnabled ? "enabled" : "disabled");
}
}
/**
* Aggregate statistics for a single package under a single provider.
*/

View File

@@ -771,10 +771,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
boolean enabled = mContext.getSystemService(LocationManager.class)
.isLocationEnabledForUser(UserHandle.CURRENT);
// .. but enable anyway, if there's an active settings-ignored request (e.g. ELS)
// .. but enable anyway, if there's an active bypass request (e.g. ELS or ADAS)
enabled |= (mProviderRequest != null
&& mProviderRequest.isActive()
&& mProviderRequest.isLocationSettingsIgnored());
&& mProviderRequest.isBypass());
// ... and, finally, disable anyway, if device is being shut down
enabled &= !mShutdown;

View File

@@ -17,6 +17,7 @@
package com.android.server.location.injector;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.settings.LocationSettings;
/**
* Injects various location dependencies so that they may be controlled by tests.
@@ -27,6 +28,9 @@ public interface Injector {
/** Returns a UserInfoHelper. */
UserInfoHelper getUserInfoHelper();
/** Returns a LocationSettings. */
LocationSettings getLocationSettings();
/** Returns an AlarmHelper. */
AlarmHelper getAlarmHelper();

View File

@@ -670,8 +670,6 @@ public class SystemSettingsHelper extends SettingsHelper {
}
}
private static class PackageTagsListSetting extends DeviceConfigSetting {
private final Supplier<ArrayMap<String, ArraySet<String>>> mBaseValuesSupplier;

View File

@@ -113,6 +113,8 @@ import com.android.server.location.injector.UserInfoHelper;
import com.android.server.location.injector.UserInfoHelper.UserListener;
import com.android.server.location.listeners.ListenerMultiplexer;
import com.android.server.location.listeners.RemoteListenerRegistration;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
@@ -548,6 +550,19 @@ public class LocationProviderManager extends
return false;
}
@GuardedBy("mLock")
final boolean onAdasGnssLocationEnabledChanged(int userId) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mLock));
}
if (getIdentity().getUserId() == userId) {
return onProviderLocationRequestChanged();
}
return false;
}
@GuardedBy("mLock")
final boolean onForegroundChanged(int uid, boolean foreground) {
if (Build.IS_DEBUGGABLE) {
@@ -592,8 +607,8 @@ public class LocationProviderManager extends
onHighPowerUsageChanged();
updateService();
// if location settings ignored has changed then the active state may have changed
return oldRequest.isLocationSettingsIgnored() != newRequest.isLocationSettingsIgnored();
// if bypass state has changed then the active state may have changed
return oldRequest.isBypass() != newRequest.isBypass();
}
private LocationRequest calculateProviderLocationRequest() {
@@ -616,9 +631,24 @@ public class LocationProviderManager extends
if (!mSettingsHelper.getIgnoreSettingsAllowlist().contains(
getIdentity().getPackageName(), getIdentity().getAttributionTag())
&& !mLocationManagerInternal.isProvider(null, getIdentity())) {
builder.setLocationSettingsIgnored(false);
locationSettingsIgnored = false;
}
builder.setLocationSettingsIgnored(locationSettingsIgnored);
}
boolean adasGnssBypass = baseRequest.isAdasGnssBypass();
if (adasGnssBypass) {
// if we are not currently allowed use adas gnss bypass, disable it
if (!GPS_PROVIDER.equals(mName)) {
Log.e(TAG, "adas gnss bypass request received in non-gps provider");
adasGnssBypass = false;
} else if (!mLocationSettings.getUserSettings(
getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
adasGnssBypass = false;
}
builder.setAdasGnssBypass(adasGnssBypass);
}
if (!locationSettingsIgnored && !isThrottlingExempt()) {
@@ -769,7 +799,7 @@ public class LocationProviderManager extends
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
getRequest().isLocationSettingsIgnored(),
getRequest().isBypass(),
maxLocationAgeMs);
if (lastLocation != null) {
executeOperation(acceptLocationChange(LocationResult.wrap(lastLocation)));
@@ -1114,7 +1144,7 @@ public class LocationProviderManager extends
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
getRequest().isLocationSettingsIgnored(),
getRequest().isBypass(),
MAX_CURRENT_LOCATION_AGE_MS);
if (lastLocation != null) {
executeOperation(acceptLocationChange(LocationResult.wrap(lastLocation)));
@@ -1267,6 +1297,7 @@ public class LocationProviderManager extends
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
protected final LocationManagerInternal mLocationManagerInternal;
protected final LocationSettings mLocationSettings;
protected final SettingsHelper mSettingsHelper;
protected final UserInfoHelper mUserHelper;
protected final AlarmHelper mAlarmHelper;
@@ -1280,6 +1311,8 @@ public class LocationProviderManager extends
protected final LocationFudger mLocationFudger;
private final UserListener mUserChangedListener = this::onUserChanged;
private final LocationSettings.LocationUserSettingsListener mLocationUserSettingsListener =
this::onLocationUserSettingsChanged;
private final UserSettingChangedListener mLocationEnabledChangedListener =
this::onLocationEnabledChanged;
private final GlobalSettingChangedListener mBackgroundThrottlePackageWhitelistChangedListener =
@@ -1332,6 +1365,7 @@ public class LocationProviderManager extends
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
mLocationSettings = injector.getLocationSettings();
mSettingsHelper = injector.getSettingsHelper();
mUserHelper = injector.getUserInfoHelper();
mAlarmHelper = injector.getAlarmHelper();
@@ -1362,6 +1396,7 @@ public class LocationProviderManager extends
mStateChangedListener = listener;
mUserHelper.addListener(mUserChangedListener);
mLocationSettings.registerLocationUserSettingsListener(mLocationUserSettingsListener);
mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
final long identity = Binder.clearCallingIdentity();
@@ -1389,6 +1424,7 @@ public class LocationProviderManager extends
}
mUserHelper.removeListener(mUserChangedListener);
mLocationSettings.unregisterLocationUserSettingsListener(mLocationUserSettingsListener);
mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
// if external entities are registering listeners it's their responsibility to
@@ -1550,7 +1586,7 @@ public class LocationProviderManager extends
public @Nullable Location getLastLocation(LastLocationRequest request,
CallerIdentity identity, @PermissionLevel int permissionLevel) {
if (!isActive(request.isLocationSettingsIgnored(), identity)) {
if (!isActive(request.isBypass(), identity)) {
return null;
}
@@ -1564,7 +1600,7 @@ public class LocationProviderManager extends
getLastLocationUnsafe(
identity.getUserId(),
permissionLevel,
request.isLocationSettingsIgnored(),
request.isBypass(),
Long.MAX_VALUE),
permissionLevel);
@@ -1584,7 +1620,7 @@ public class LocationProviderManager extends
* location if necessary.
*/
public @Nullable Location getLastLocationUnsafe(int userId,
@PermissionLevel int permissionLevel, boolean ignoreLocationSettings,
@PermissionLevel int permissionLevel, boolean isBypass,
long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
// find the most recent location across all users
@@ -1592,7 +1628,7 @@ public class LocationProviderManager extends
final int[] runningUserIds = mUserHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
Location next = getLastLocationUnsafe(runningUserIds[i], permissionLevel,
ignoreLocationSettings, maximumAgeMs);
isBypass, maximumAgeMs);
if (lastLocation == null || (next != null && next.getElapsedRealtimeNanos()
> lastLocation.getElapsedRealtimeNanos())) {
lastLocation = next;
@@ -1601,7 +1637,7 @@ public class LocationProviderManager extends
return lastLocation;
} else if (userId == UserHandle.USER_CURRENT) {
return getLastLocationUnsafe(mUserHelper.getCurrentUserId(), permissionLevel,
ignoreLocationSettings, maximumAgeMs);
isBypass, maximumAgeMs);
}
Preconditions.checkArgument(userId >= 0);
@@ -1613,7 +1649,7 @@ public class LocationProviderManager extends
if (lastLocation == null) {
location = null;
} else {
location = lastLocation.get(permissionLevel, ignoreLocationSettings);
location = lastLocation.get(permissionLevel, isBypass);
}
}
@@ -1925,7 +1961,7 @@ public class LocationProviderManager extends
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
long delayMs;
if (!oldRequest.isLocationSettingsIgnored() && newRequest.isLocationSettingsIgnored()) {
if (!oldRequest.isBypass() && newRequest.isBypass()) {
delayMs = 0;
} else if (newRequest.getIntervalMillis() > oldRequest.getIntervalMillis()) {
// if the interval has increased, tell the provider immediately, so it can save power
@@ -2002,12 +2038,12 @@ public class LocationProviderManager extends
return false;
}
boolean locationSettingsIgnored = registration.getRequest().isLocationSettingsIgnored();
if (!isActive(locationSettingsIgnored, registration.getIdentity())) {
boolean isBypass = registration.getRequest().isBypass();
if (!isActive(isBypass, registration.getIdentity())) {
return false;
}
if (!locationSettingsIgnored) {
if (!isBypass) {
switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
case LOCATION_MODE_FOREGROUND_ONLY:
if (!registration.isForeground()) {
@@ -2036,15 +2072,15 @@ public class LocationProviderManager extends
return true;
}
private boolean isActive(boolean locationSettingsIgnored, CallerIdentity identity) {
private boolean isActive(boolean isBypass, CallerIdentity identity) {
if (identity.isSystemServer()) {
if (!locationSettingsIgnored) {
if (!isBypass) {
if (!isEnabled(mUserHelper.getCurrentUserId())) {
return false;
}
}
} else {
if (!locationSettingsIgnored) {
if (!isBypass) {
if (!isEnabled(identity.getUserId())) {
return false;
}
@@ -2071,6 +2107,7 @@ public class LocationProviderManager extends
long intervalMs = ProviderRequest.INTERVAL_DISABLED;
int quality = LocationRequest.QUALITY_LOW_POWER;
long maxUpdateDelayMs = Long.MAX_VALUE;
boolean adasGnssBypass = false;
boolean locationSettingsIgnored = false;
boolean lowPower = true;
@@ -2086,6 +2123,7 @@ public class LocationProviderManager extends
intervalMs = min(request.getIntervalMillis(), intervalMs);
quality = min(request.getQuality(), quality);
maxUpdateDelayMs = min(request.getMaxUpdateDelayMillis(), maxUpdateDelayMs);
adasGnssBypass |= request.isAdasGnssBypass();
locationSettingsIgnored |= request.isLocationSettingsIgnored();
lowPower &= request.isLowPower();
}
@@ -2123,6 +2161,7 @@ public class LocationProviderManager extends
.setIntervalMillis(intervalMs)
.setQuality(quality)
.setMaxUpdateDelayMillis(maxUpdateDelayMs)
.setAdasGnssBypass(adasGnssBypass)
.setLocationSettingsIgnored(locationSettingsIgnored)
.setLowPower(lowPower)
.setWorkSource(workSource)
@@ -2191,6 +2230,16 @@ public class LocationProviderManager extends
}
}
private void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
if (oldSettings.isAdasGnssLocationEnabled() != newSettings.isAdasGnssLocationEnabled()) {
synchronized (mLock) {
updateRegistrations(
registration -> registration.onAdasGnssLocationEnabledChanged(userId));
}
}
}
private void onLocationEnabledChanged(int userId) {
synchronized (mLock) {
if (mState == STATE_STOPPED) {
@@ -2560,16 +2609,16 @@ public class LocationProviderManager extends
}
public @Nullable Location get(@PermissionLevel int permissionLevel,
boolean ignoreLocationSettings) {
boolean isBypass) {
switch (permissionLevel) {
case PERMISSION_FINE:
if (ignoreLocationSettings) {
if (isBypass) {
return mFineBypassLocation;
} else {
return mFineLocation;
}
case PERMISSION_COARSE:
if (ignoreLocationSettings) {
if (isBypass) {
return mCoarseBypassLocation;
} else {
return mCoarseLocation;

View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2021 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.settings;
import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import android.content.Context;
import android.os.Environment;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.FgThread;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
/**
* Accessor for location user settings. Ensure there is only ever one instance as multiple instances
* don't play nicely with each other.
*/
public class LocationSettings {
/** Listens for changes to location user settings. */
public interface LocationUserSettingsListener {
/** Invoked when location user settings have changed for the given user. */
void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
LocationUserSettings newSettings);
}
private static final String LOCATION_DIRNAME = "location";
private static final String LOCATION_SETTINGS_FILENAME = "settings";
final Context mContext;
@GuardedBy("mUserSettings")
private final SparseArray<LocationUserSettingsStore> mUserSettings;
private final CopyOnWriteArrayList<LocationUserSettingsListener> mUserSettingsListeners;
public LocationSettings(Context context) {
mContext = context;
mUserSettings = new SparseArray<>(1);
mUserSettingsListeners = new CopyOnWriteArrayList<>();
}
/** Registers a listener for changes to location user settings. */
public final void registerLocationUserSettingsListener(LocationUserSettingsListener listener) {
mUserSettingsListeners.add(listener);
}
/** Unregisters a listener for changes to location user settings. */
public final void unregisterLocationUserSettingsListener(
LocationUserSettingsListener listener) {
mUserSettingsListeners.remove(listener);
}
protected File getUserSettingsDir(int userId) {
return Environment.getDataSystemDeDirectory(userId);
}
protected LocationUserSettingsStore createUserSettingsStore(int userId, File file) {
return new LocationUserSettingsStore(userId, file);
}
private LocationUserSettingsStore getUserSettingsStore(int userId) {
synchronized (mUserSettings) {
LocationUserSettingsStore settingsStore = mUserSettings.get(userId);
if (settingsStore == null) {
File file = new File(new File(getUserSettingsDir(userId), LOCATION_DIRNAME),
LOCATION_SETTINGS_FILENAME);
settingsStore = createUserSettingsStore(userId, file);
mUserSettings.put(userId, settingsStore);
}
return settingsStore;
}
}
/** Retrieves the current state of location user settings. */
public final LocationUserSettings getUserSettings(int userId) {
return getUserSettingsStore(userId).get();
}
/** Updates the current state of location user settings for the given user. */
public final void updateUserSettings(int userId,
Function<LocationUserSettings, LocationUserSettings> updater) {
getUserSettingsStore(userId).update(updater);
}
@VisibleForTesting
final void flushFiles() throws InterruptedException {
synchronized (mUserSettings) {
int size = mUserSettings.size();
for (int i = 0; i < size; i++) {
mUserSettings.valueAt(i).flushFile();
}
}
}
@VisibleForTesting
final void deleteFiles() throws InterruptedException {
synchronized (mUserSettings) {
int size = mUserSettings.size();
for (int i = 0; i < size; i++) {
mUserSettings.valueAt(i).deleteFile();
}
}
}
protected final void fireListeners(int userId, LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
for (LocationUserSettingsListener listener : mUserSettingsListeners) {
listener.onLocationUserSettingsChanged(userId, oldSettings, newSettings);
}
}
class LocationUserSettingsStore extends SettingsStore<LocationUserSettings> {
protected final int mUserId;
LocationUserSettingsStore(int userId, File file) {
super(file);
mUserId = userId;
}
@Override
protected LocationUserSettings read(int version, DataInput in) throws IOException {
return filterSettings(LocationUserSettings.read(mContext.getResources(), version, in));
}
@Override
protected void write(DataOutput out, LocationUserSettings settings) throws IOException {
settings.write(out);
}
@Override
public void update(Function<LocationUserSettings, LocationUserSettings> updater) {
super.update(settings -> filterSettings(updater.apply(settings)));
}
@Override
protected void onChange(LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
FgThread.getExecutor().execute(() -> fireListeners(mUserId, oldSettings, newSettings));
}
private LocationUserSettings filterSettings(LocationUserSettings settings) {
if (settings.isAdasGnssLocationEnabled()
&& !mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
// prevent non-automotive devices from ever enabling this
settings = settings.withAdasGnssLocationEnabled(false);
}
return settings;
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2021 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.settings;
import android.content.res.Resources;
import com.android.internal.R;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Objects;
/** Holds the state of location user settings. */
public final class LocationUserSettings implements SettingsStore.VersionedSettings {
// remember to bump this version code and add the appropriate upgrade logic whenever the format
// is changed.
private static final int VERSION = 1;
private final boolean mAdasGnssLocationEnabled;
private LocationUserSettings(boolean adasGnssLocationEnabled) {
mAdasGnssLocationEnabled = adasGnssLocationEnabled;
}
@Override
public int getVersion() {
return VERSION;
}
public boolean isAdasGnssLocationEnabled() {
return mAdasGnssLocationEnabled;
}
/** Returns an instance with ADAS GNSS location enabled state set as given. */
public LocationUserSettings withAdasGnssLocationEnabled(boolean adasEnabled) {
if (adasEnabled == mAdasGnssLocationEnabled) {
return this;
}
return new LocationUserSettings(adasEnabled);
}
void write(DataOutput out) throws IOException {
out.writeBoolean(mAdasGnssLocationEnabled);
}
static LocationUserSettings read(Resources resources, int version, DataInput in)
throws IOException {
boolean adasGnssLocationEnabled;
// upgrade code goes here. remember to bump the version field when changing the format
switch (version) {
default:
// set all fields to defaults
adasGnssLocationEnabled = resources.getBoolean(
R.bool.config_defaultAdasGnssLocationEnabled);
break;
case 1:
adasGnssLocationEnabled = in.readBoolean();
// fall through
}
return new LocationUserSettings(adasGnssLocationEnabled);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof LocationUserSettings)) {
return false;
}
LocationUserSettings that = (LocationUserSettings) o;
return mAdasGnssLocationEnabled == that.mAdasGnssLocationEnabled;
}
@Override
public int hashCode() {
return Objects.hash(mAdasGnssLocationEnabled);
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright (C) 2021 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.settings;
import static com.android.server.location.LocationManagerService.TAG;
import static com.android.server.location.settings.SettingsStore.VersionedSettings.VERSION_DOES_NOT_EXIST;
import android.util.AtomicFile;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.Preconditions;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
/** Base class for read/write/versioning functionality for storing persistent settings to a file. */
abstract class SettingsStore<T extends SettingsStore.VersionedSettings> {
interface VersionedSettings {
/** Represents that the settings do not exist. */
int VERSION_DOES_NOT_EXIST = Integer.MAX_VALUE;
/** Must always return a version number less than {@link #VERSION_DOES_NOT_EXIST}. */
int getVersion();
}
private final AtomicFile mFile;
@GuardedBy("this")
private boolean mInitialized;
@GuardedBy("this")
private T mCache;
protected SettingsStore(File file) {
mFile = new AtomicFile(file);
}
/**
* Must be implemented to read in a settings instance, and upgrade to the appropriate version
* where necessary. If the provided version is {@link VersionedSettings#VERSION_DOES_NOT_EXIST}
* then the DataInput will be empty, and the method should return a settings instance with all
* settings set to the default value.
*/
protected abstract T read(int version, DataInput in) throws IOException;
/**
* Must be implemented to write the given settings to the given DataOutput.
*/
protected abstract void write(DataOutput out, T settings) throws IOException;
/**
* Invoked when settings change, and while holding the internal lock. If used to invoke
* listeners, ensure they are not invoked while holding the lock (ie, asynchronously).
*/
protected abstract void onChange(T oldSettings, T newSettings);
public final synchronized void initializeCache() {
if (!mInitialized) {
if (mFile.exists()) {
try (DataInputStream is = new DataInputStream(mFile.openRead())) {
mCache = read(is.readInt(), is);
Preconditions.checkState(mCache.getVersion() < VERSION_DOES_NOT_EXIST);
} catch (IOException e) {
Log.e(TAG, "error reading location settings (" + mFile
+ "), falling back to defaults", e);
}
}
if (mCache == null) {
try {
mCache = read(VERSION_DOES_NOT_EXIST,
new DataInputStream(new ByteArrayInputStream(new byte[0])));
Preconditions.checkState(mCache.getVersion() < VERSION_DOES_NOT_EXIST);
} catch (IOException e) {
throw new AssertionError(e);
}
}
mInitialized = true;
}
}
public final synchronized T get() {
initializeCache();
return mCache;
}
public synchronized void update(Function<T, T> updater) {
initializeCache();
T oldSettings = mCache;
T newSettings = Objects.requireNonNull(updater.apply(oldSettings));
if (oldSettings.equals(newSettings)) {
return;
}
mCache = newSettings;
Preconditions.checkState(mCache.getVersion() < VERSION_DOES_NOT_EXIST);
writeLazily(newSettings);
onChange(oldSettings, newSettings);
}
@VisibleForTesting
synchronized void flushFile() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
BackgroundThread.getExecutor().execute(latch::countDown);
latch.await();
}
@VisibleForTesting
synchronized void deleteFile() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
BackgroundThread.getExecutor().execute(() -> {
mFile.delete();
latch.countDown();
});
latch.await();
}
private void writeLazily(T settings) {
BackgroundThread.getExecutor().execute(() -> {
FileOutputStream os = null;
try {
os = mFile.startWrite();
DataOutputStream out = new DataOutputStream(os);
out.writeInt(settings.getVersion());
write(out, settings);
mFile.finishWrite(os);
} catch (IOException e) {
mFile.failWrite(os);
Log.e(TAG, "failure serializing location settings", e);
} catch (Throwable e) {
mFile.failWrite(os);
throw e;
}
});
}
}

View File

@@ -18,6 +18,7 @@ package com.android.server.location.gnss;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -49,6 +50,7 @@ public class GnssGeofenceProxyTest {
private static final int NOTIFICATION_RESPONSIVENESS = 0;
private static final int UNKNOWN_TIMER = 0;
private @Mock Context mContext;
private @Mock GnssConfiguration mMockConfiguration;
private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
@@ -63,7 +65,7 @@ public class GnssGeofenceProxyTest {
GnssNative.setGnssHalForTest(mFakeHal);
GnssNative gnssNative = Objects.requireNonNull(
GnssNative.create(new TestInjector(), mMockConfiguration));
GnssNative.create(new TestInjector(mContext), mMockConfiguration));
gnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
mTestProvider = new GnssGeofenceProxy(gnssNative);
gnssNative.register();

View File

@@ -29,9 +29,10 @@ import java.util.HashMap;
public class FakeAppOpsHelper extends AppOpsHelper {
private static class AppOp {
private boolean mAllowed = true;
private boolean mStarted = false;
private int mNoteCount = 0;
AppOp() {}
boolean mAllowed = true;
boolean mStarted = false;
int mNoteCount = 0;
}
private final HashMap<String, SparseArray<AppOp>> mAppOps;

View File

@@ -29,8 +29,8 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Version of AppOpsHelper for testing. Settings are initialized to reasonable defaults (location is
* enabled by default).
* Version of SettingsHelper for testing. Settings are initialized to reasonable defaults (location
* is enabled by default).
*/
public class FakeSettingsHelper extends SettingsHelper {

View File

@@ -16,9 +16,14 @@
package com.android.server.location.injector;
import android.content.Context;
import com.android.server.location.settings.FakeLocationSettings;
public class TestInjector implements Injector {
private final FakeUserInfoHelper mUserInfoHelper;
private final FakeLocationSettings mLocationSettings;
private final FakeAlarmHelper mAlarmHelper;
private final FakeAppOpsHelper mAppOpsHelper;
private final FakeLocationPermissionsHelper mLocationPermissionsHelper;
@@ -32,8 +37,9 @@ public class TestInjector implements Injector {
private final FakeEmergencyHelper mEmergencyHelper;
private final LocationUsageLogger mLocationUsageLogger;
public TestInjector() {
public TestInjector(Context context) {
mUserInfoHelper = new FakeUserInfoHelper();
mLocationSettings = new FakeLocationSettings(context);
mAlarmHelper = new FakeAlarmHelper();
mAppOpsHelper = new FakeAppOpsHelper();
mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
@@ -53,6 +59,11 @@ public class TestInjector implements Injector {
return mUserInfoHelper;
}
@Override
public FakeLocationSettings getLocationSettings() {
return mLocationSettings;
}
@Override
public FakeAlarmHelper getAlarmHelper() {
return mAlarmHelper;

View File

@@ -19,6 +19,8 @@ package com.android.server.location.provider;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationRequest.PASSIVE_INTERVAL;
import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
@@ -56,6 +58,8 @@ import static org.mockito.MockitoAnnotations.initMocks;
import static org.testng.Assert.assertThrows;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.location.ILocationCallback;
import android.location.ILocationListener;
import android.location.LastLocationRequest;
@@ -82,6 +86,7 @@ import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.location.injector.FakeUserInfoHelper;
@@ -139,6 +144,10 @@ public class LocationProviderManagerTest {
@Mock
private Context mContext;
@Mock
private Resources mResources;
@Mock
private PackageManager mPackageManager;
@Mock
private PowerManager mPowerManager;
@Mock
private PowerManager.WakeLock mWakeLock;
@@ -161,20 +170,28 @@ public class LocationProviderManagerTest {
LocalServices.addService(LocationManagerInternal.class, mInternal);
doReturn("android").when(mContext).getPackageName();
doReturn(mResources).when(mContext).getResources();
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
mInjector = new TestInjector();
mInjector = new TestInjector(mContext);
mInjector.getUserInfoHelper().startUser(OTHER_USER);
mPassive = new PassiveLocationProviderManager(mContext, mInjector);
mPassive.startManager(null);
mPassive.setRealProvider(new PassiveLocationProvider(mContext));
createManager(NAME);
}
private void createManager(String name) {
mStateChangedListener = mock(LocationProviderManager.StateChangedListener.class);
mProvider = new TestProvider(PROPERTIES, PROVIDER_IDENTITY);
mProvider.setProviderAllowed(true);
mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive);
mManager = new LocationProviderManager(mContext, mInjector, name, mPassive);
mManager.startManager(mStateChangedListener);
mManager.setRealProvider(mProvider);
}
@@ -1016,6 +1033,95 @@ public class LocationProviderManagerTest {
assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(5);
}
@Test
public void testProviderRequest_AdasGnssBypass() {
doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
createManager(GPS_PROVIDER);
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = new LocationRequest.Builder(5)
.setWorkSource(WORK_SOURCE)
.build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().isActive()).isTrue();
assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(5);
assertThat(mProvider.getRequest().isAdasGnssBypass()).isFalse();
ILocationListener listener2 = createMockLocationListener();
LocationRequest request2 = new LocationRequest.Builder(1)
.setAdasGnssBypass(true)
.setWorkSource(WORK_SOURCE)
.build();
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
assertThat(mProvider.getRequest().isActive()).isTrue();
assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(1);
assertThat(mProvider.getRequest().isAdasGnssBypass()).isTrue();
}
@Test
public void testProviderRequest_AdasGnssBypass_ProviderDisabled() {
doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
createManager(GPS_PROVIDER);
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = new LocationRequest.Builder(1)
.setWorkSource(WORK_SOURCE)
.build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
ILocationListener listener2 = createMockLocationListener();
LocationRequest request2 = new LocationRequest.Builder(5)
.setAdasGnssBypass(true)
.setWorkSource(WORK_SOURCE)
.build();
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
mInjector.getSettingsHelper().setLocationEnabled(false, IDENTITY.getUserId());
assertThat(mProvider.getRequest().isActive()).isTrue();
assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(5);
assertThat(mProvider.getRequest().isAdasGnssBypass()).isTrue();
}
@Test
public void testProviderRequest_AdasGnssBypass_ProviderDisabled_AdasDisabled() {
mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
new PackageTagsList.Builder().add(
IDENTITY.getPackageName()).build());
doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
createManager(GPS_PROVIDER);
ILocationListener listener1 = createMockLocationListener();
LocationRequest request1 = new LocationRequest.Builder(5)
.setLocationSettingsIgnored(true)
.setWorkSource(WORK_SOURCE)
.build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
ILocationListener listener2 = createMockLocationListener();
LocationRequest request2 = new LocationRequest.Builder(1)
.setAdasGnssBypass(true)
.setWorkSource(WORK_SOURCE)
.build();
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
mInjector.getLocationSettings().updateUserSettings(IDENTITY.getUserId(),
settings -> settings.withAdasGnssLocationEnabled(false));
mInjector.getSettingsHelper().setLocationEnabled(false, IDENTITY.getUserId());
assertThat(mProvider.getRequest().isActive()).isTrue();
assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(5);
assertThat(mProvider.getRequest().isAdasGnssBypass()).isFalse();
}
@Test
public void testProviderRequest_BatterySaver_ScreenOnOff() {
mInjector.getLocationPowerSaveModeHelper().setLocationPowerSaveMode(

View File

@@ -27,6 +27,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import android.content.Context;
import android.location.Location;
import android.location.LocationResult;
import android.location.provider.ProviderRequest;
@@ -58,6 +59,7 @@ public class StationaryThrottlingLocationProviderTest {
private TestInjector mInjector;
private FakeProvider mDelegateProvider;
private @Mock Context mContext;
private @Mock AbstractLocationProvider.Listener mListener;
private @Mock FakeProvider.FakeProviderInterface mDelegate;
@@ -72,7 +74,7 @@ public class StationaryThrottlingLocationProviderTest {
mRandom = new Random(seed);
mInjector = new TestInjector();
mInjector = new TestInjector(mContext);
mDelegateProvider = new FakeProvider(mDelegate);
mProvider = new StationaryThrottlingLocationProvider("test_provider", mInjector,

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2021 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.settings;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import java.io.File;
public class FakeLocationSettings extends LocationSettings {
public FakeLocationSettings(Context context) {
super(context);
}
@Override
protected File getUserSettingsDir(int userId) {
return ApplicationProvider.getApplicationContext().getCacheDir();
}
@Override
protected LocationUserSettingsStore createUserSettingsStore(int userId, File file) {
return new FakeLocationUserSettingsStore(userId, file);
}
private class FakeLocationUserSettingsStore extends LocationUserSettingsStore {
FakeLocationUserSettingsStore(int userId, File file) {
super(userId, file);
}
@Override
protected void onChange(LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
fireListeners(mUserId, oldSettings, newSettings);
}
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2021 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.settings;
import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.platform.test.annotations.Presubmit;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.io.File;
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LocationSettingsTest {
private @Mock Context mContext;
private @Mock Resources mResources;
private @Mock PackageManager mPackageManager;
private LocationSettings mLocationSettings;
@Before
public void setUp() {
initMocks(this);
doReturn(mResources).when(mContext).getResources();
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
resetLocationSettings();
}
@After
public void tearDown() throws Exception {
mLocationSettings.deleteFiles();
}
private void resetLocationSettings() {
mLocationSettings = new LocationSettings(mContext) {
@Override
protected File getUserSettingsDir(int userId) {
return ApplicationProvider.getApplicationContext().getCacheDir();
}
};
}
@Test
public void testLoadDefaults() {
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
assertThat(mLocationSettings.getUserSettings(1).isAdasGnssLocationEnabled()).isTrue();
doReturn(false).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
assertThat(mLocationSettings.getUserSettings(2).isAdasGnssLocationEnabled()).isFalse();
}
@Test
public void testUpdate() {
doReturn(false).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
mLocationSettings.updateUserSettings(1,
settings -> settings.withAdasGnssLocationEnabled(true));
assertThat(mLocationSettings.getUserSettings(1).isAdasGnssLocationEnabled()).isTrue();
mLocationSettings.updateUserSettings(1,
settings -> settings.withAdasGnssLocationEnabled(false));
assertThat(mLocationSettings.getUserSettings(1).isAdasGnssLocationEnabled()).isFalse();
}
@Test
public void testSerialization() throws Exception {
doReturn(false).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
mLocationSettings.updateUserSettings(1,
settings -> settings.withAdasGnssLocationEnabled(true));
assertThat(mLocationSettings.getUserSettings(1).isAdasGnssLocationEnabled()).isTrue();
mLocationSettings.flushFiles();
resetLocationSettings();
assertThat(mLocationSettings.getUserSettings(1).isAdasGnssLocationEnabled()).isTrue();
}
@Test
public void testListeners() {
doReturn(false).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
LocationSettings.LocationUserSettingsListener listener = mock(
LocationSettings.LocationUserSettingsListener.class);
mLocationSettings.registerLocationUserSettingsListener(listener);
ArgumentCaptor<LocationUserSettings> oldCaptor = ArgumentCaptor.forClass(
LocationUserSettings.class);
ArgumentCaptor<LocationUserSettings> newCaptor = ArgumentCaptor.forClass(
LocationUserSettings.class);
mLocationSettings.updateUserSettings(1,
settings -> settings.withAdasGnssLocationEnabled(true));
verify(listener, timeout(500).times(1)).onLocationUserSettingsChanged(eq(1),
oldCaptor.capture(), newCaptor.capture());
assertThat(oldCaptor.getValue().isAdasGnssLocationEnabled()).isFalse();
assertThat(newCaptor.getValue().isAdasGnssLocationEnabled()).isTrue();
oldCaptor = ArgumentCaptor.forClass(LocationUserSettings.class);
newCaptor = ArgumentCaptor.forClass(LocationUserSettings.class);
mLocationSettings.updateUserSettings(1,
settings -> settings.withAdasGnssLocationEnabled(false));
verify(listener, timeout(500).times(2)).onLocationUserSettingsChanged(eq(1),
oldCaptor.capture(), newCaptor.capture());
assertThat(oldCaptor.getValue().isAdasGnssLocationEnabled()).isTrue();
assertThat(newCaptor.getValue().isAdasGnssLocationEnabled()).isFalse();
mLocationSettings.unregisterLocationUserSettingsListener(listener);
mLocationSettings.updateUserSettings(1,
settings -> settings.withAdasGnssLocationEnabled(true));
verify(listener, after(500).times(2)).onLocationUserSettingsChanged(anyInt(), any(), any());
}
@Test
public void testNonAutomotive() {
doReturn(false).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
LocationSettings.LocationUserSettingsListener listener = mock(
LocationSettings.LocationUserSettingsListener.class);
mLocationSettings.registerLocationUserSettingsListener(listener);
assertThat(mLocationSettings.getUserSettings(1).isAdasGnssLocationEnabled()).isFalse();
mLocationSettings.updateUserSettings(1,
settings -> settings.withAdasGnssLocationEnabled(true));
assertThat(mLocationSettings.getUserSettings(1).isAdasGnssLocationEnabled()).isFalse();
verify(listener, after(500).never()).onLocationUserSettingsChanged(anyInt(), any(), any());
}
}