diff options
author | Soonil Nagarkar <sooniln@google.com> | 2021-06-16 15:03:05 -0700 |
---|---|---|
committer | Soonil Nagarkar <sooniln@google.com> | 2021-06-23 11:11:50 -0700 |
commit | e58fea5a45586b5a81d6ef8da3bcd132206c3b9b (patch) | |
tree | 7b61c26d859ef8cd740fb3c16afb426b810fe35b | |
parent | d616f1ed950b62f5a6682957d483f46f9547c3ed (diff) |
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
25 files changed, 1378 insertions, 92 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f4aff9467cee..424505a426b6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -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. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5f849b4e1eb0..8be260e5dcee 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -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" /> diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index c9e4e0a9cb92..5d5c0fc6265d 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -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); diff --git a/location/java/android/location/LastLocationRequest.java b/location/java/android/location/LastLocationRequest.java index 9ea8048ad476..0970c1c76a36 100644 --- a/location/java/android/location/LastLocationRequest.java +++ b/location/java/android/location/LastLocationRequest.java @@ -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; } @@ -56,6 +59,21 @@ public final class LastLocationRequest implements Parcelable { } /** + * 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; } @@ -164,6 +200,25 @@ public final class LastLocationRequest implements Parcelable { } /** + * 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 * last location request. This is only intended for use in user initiated emergency @@ -186,6 +241,7 @@ public final class LastLocationRequest implements Parcelable { public @NonNull LastLocationRequest build() { return new LastLocationRequest( mHiddenFromAppOps, + mAdasGnssBypass, mLocationSettingsIgnored); } } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index ae44c5e34521..526b84e85e38 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -315,6 +315,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 * active location requests should be read from AppOpsManager using @@ -621,6 +648,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}. * diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index a3842a1ffd0a..b48e59676ac1 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -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: @@ -648,6 +651,21 @@ public final class LocationRequest implements Parcelable { } /** + * 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. */ @@ -673,6 +691,15 @@ public final class LocationRequest implements Parcelable { } /** + * 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) { @@ -1092,6 +1128,25 @@ public final class LocationRequest implements Parcelable { } /** + * 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 * request. This is only intended for use in user initiated emergency situations, and @@ -1171,6 +1226,7 @@ public final class LocationRequest implements Parcelable { mMinUpdateDistanceMeters, mMaxUpdateDelayMillis, mHiddenFromAppOps, + mAdasGnssBypass, mLocationSettingsIgnored, mLowPower, new WorkSource(mWorkSource)); diff --git a/location/java/android/location/provider/ProviderRequest.java b/location/java/android/location/provider/ProviderRequest.java index b72d36519e72..4f33a529a812 100644 --- a/location/java/android/location/provider/ProviderRequest.java +++ b/location/java/android/location/provider/ProviderRequest.java @@ -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); } @@ -126,6 +135,18 @@ public final class ProviderRequest implements Parcelable { } /** + * 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 * ability. This should only be used in case of a user initiated emergency. @@ -135,6 +156,15 @@ public final class ProviderRequest implements Parcelable { } /** + * 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. */ public @NonNull WorkSource getWorkSource() { @@ -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(); @@ -299,6 +337,16 @@ public final class ProviderRequest implements Parcelable { } /** + * 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. */ public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) { @@ -326,6 +374,7 @@ public final class ProviderRequest implements Parcelable { mQuality, mMaxUpdateDelayMillis, mLowPower, + mAdasGnssBypass, mLocationSettingsIgnored, mWorkSource); } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index aeb1893c78b6..e6210b2a8cc9 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -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; @@ -1126,6 +1232,24 @@ public class LocationManagerService extends ILocationManager.Stub implements } @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, @@ -1621,6 +1747,11 @@ public class LocationManagerService extends ILocationManager.Stub implements } @Override + public LocationSettings getLocationSettings() { + return mLocationSettings; + } + + @Override public AlarmHelper getAlarmHelper() { return mAlarmHelper; } diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java index 937849309f96..b65338d9691d 100644 --- a/services/core/java/com/android/server/location/LocationShellCommand.java +++ b/services/core/java/com/android/server/location/LocationShellCommand.java @@ -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"); } } diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java index e6d25ece93ef..db2a43f7a00d 100644 --- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java @@ -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. */ diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 1cccf08b0367..f3dcfbbf2c0a 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -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; diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java index b0351184ffe5..173fd13c11a1 100644 --- a/services/core/java/com/android/server/location/injector/Injector.java +++ b/services/core/java/com/android/server/location/injector/Injector.java @@ -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(); diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java index c315da476d99..3e8da7d7478a 100644 --- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java @@ -670,8 +670,6 @@ public class SystemSettingsHelper extends SettingsHelper { } } - - private static class PackageTagsListSetting extends DeviceConfigSetting { private final Supplier<ArrayMap<String, ArraySet<String>>> mBaseValuesSupplier; diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 8d335b83d99c..43886f7cb87b 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -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; @@ -549,6 +551,19 @@ public class LocationProviderManager extends } @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) { Preconditions.checkState(Thread.holdsLock(mLock)); @@ -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; diff --git a/services/core/java/com/android/server/location/settings/LocationSettings.java b/services/core/java/com/android/server/location/settings/LocationSettings.java new file mode 100644 index 000000000000..d52153893970 --- /dev/null +++ b/services/core/java/com/android/server/location/settings/LocationSettings.java @@ -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; + } + } +} diff --git a/services/core/java/com/android/server/location/settings/LocationUserSettings.java b/services/core/java/com/android/server/location/settings/LocationUserSettings.java new file mode 100644 index 000000000000..283255ef4b22 --- /dev/null +++ b/services/core/java/com/android/server/location/settings/LocationUserSettings.java @@ -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); + } +} diff --git a/services/core/java/com/android/server/location/settings/SettingsStore.java b/services/core/java/com/android/server/location/settings/SettingsStore.java new file mode 100644 index 000000000000..01338a3129da --- /dev/null +++ b/services/core/java/com/android/server/location/settings/SettingsStore.java @@ -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; + } + }); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssGeofenceProxyTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssGeofenceProxyTest.java index b480f24fb371..5e219a25ed9a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssGeofenceProxyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssGeofenceProxyTest.java @@ -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(); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java index 3d0378170c94..d728451d92b9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java @@ -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; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java index f1099f0e8184..cd70020f5c28 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java @@ -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 { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java index ae70dadba041..bd24cfd78a2c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java @@ -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); @@ -54,6 +60,11 @@ public class TestInjector implements Injector { } @Override + public FakeLocationSettings getLocationSettings() { + return mLocationSettings; + } + + @Override public FakeAlarmHelper getAlarmHelper() { return mAlarmHelper; } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index 6bc3b6041070..f703e2e59181 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -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); } @@ -1017,6 +1034,95 @@ public class LocationProviderManagerTest { } @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( LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java index 04e0151e619a..63996f0e021c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java @@ -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, diff --git a/services/tests/mockingservicestests/src/com/android/server/location/settings/FakeLocationSettings.java b/services/tests/mockingservicestests/src/com/android/server/location/settings/FakeLocationSettings.java new file mode 100644 index 000000000000..4d46abaa6733 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/settings/FakeLocationSettings.java @@ -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); + } + } +} + diff --git a/services/tests/mockingservicestests/src/com/android/server/location/settings/LocationSettingsTest.java b/services/tests/mockingservicestests/src/com/android/server/location/settings/LocationSettingsTest.java new file mode 100644 index 000000000000..4b6c79b954cd --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/settings/LocationSettingsTest.java @@ -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()); + } +} |