From 8df02f452c9f1ec4cf3ddb74fe2fbe59e88905a2 Mon Sep 17 00:00:00 2001 From: Soonil Nagarkar Date: Wed, 8 Jan 2020 13:23:26 -0800 Subject: [PATCH] Refactor providers/managers Refactor how providers/managers are implemented in LMS, substantially simplifying code, and opening up further simplification around permissions in the next change. Also, limit the number of additional packages providers can have, and merge WorkSource into ProviderRequest so that providers can pass just ProviderRequests around, not both. Test: presubmits Change-Id: Ic6fab46883cf53eb26368b54a824feac39c57a32 --- location/java/android/location/Criteria.java | 325 ++++--- .../internal/location/ProviderProperties.java | 162 +++- .../internal/location/ProviderRequest.java | 128 ++- .../android/server/GnssManagerService.java | 9 +- .../server/LocationManagerService.java | 886 ++++++++---------- .../location/AbstractLocationProvider.java | 335 +++++-- .../server/location/GnssLocationProvider.java | 39 +- .../location/LocationProviderProxy.java | 129 +-- .../location/LocationSettingsStore.java | 29 + .../android/server/location/MockProvider.java | 35 +- .../location/MockableLocationProvider.java | 289 ++++++ .../server/location/PassiveProvider.java | 26 +- 12 files changed, 1477 insertions(+), 915 deletions(-) create mode 100644 services/core/java/com/android/server/location/MockableLocationProvider.java diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java index 1370b1095ae1d..26f73f7848799 100644 --- a/location/java/android/location/Criteria.java +++ b/location/java/android/location/Criteria.java @@ -16,9 +16,16 @@ package android.location; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A class indicating the application criteria for selecting a * location provider. Providers may be ordered according to accuracy, @@ -26,6 +33,25 @@ import android.os.Parcelable; * cost. */ public class Criteria implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, POWER_HIGH}) + public @interface PowerRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH}) + public @interface AccuracyRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, ACCURACY_FINE, ACCURACY_COARSE}) + public @interface LocationAccuracyRequirement { + } + /** * A constant indicating that the application does not choose to * place requirement on a particular feature. @@ -81,15 +107,15 @@ public class Criteria implements Parcelable { */ public static final int ACCURACY_HIGH = 3; - private int mHorizontalAccuracy = NO_REQUIREMENT; - private int mVerticalAccuracy = NO_REQUIREMENT; - private int mSpeedAccuracy = NO_REQUIREMENT; - private int mBearingAccuracy = NO_REQUIREMENT; - private int mPowerRequirement = NO_REQUIREMENT; - private boolean mAltitudeRequired = false; - private boolean mBearingRequired = false; - private boolean mSpeedRequired = false; - private boolean mCostAllowed = false; + private int mHorizontalAccuracy = NO_REQUIREMENT; + private int mVerticalAccuracy = NO_REQUIREMENT; + private int mSpeedAccuracy = NO_REQUIREMENT; + private int mBearingAccuracy = NO_REQUIREMENT; + private int mPowerRequirement = NO_REQUIREMENT; + private boolean mAltitudeRequired = false; + private boolean mBearingRequired = false; + private boolean mSpeedRequired = false; + private boolean mCostAllowed = false; /** * Constructs a new Criteria object. The new object will have no @@ -97,7 +123,8 @@ public class Criteria implements Parcelable { * require altitude, speed, or bearing; and will not allow monetary * cost. */ - public Criteria() {} + public Criteria() { + } /** * Constructs a new Criteria object that is a copy of the given criteria. @@ -115,125 +142,121 @@ public class Criteria implements Parcelable { } /** - * Indicates the desired horizontal accuracy (latitude and longitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired horizontal accuracy (latitude and longitude). Accuracy may be + * {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or + * {@link #NO_REQUIREMENT}. More accurate location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setHorizontalAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mHorizontalAccuracy = accuracy; + public void setHorizontalAccuracy(@AccuracyRequirement int accuracy) { + mHorizontalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired horizontal accuracy (latitude and longitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. + * + * @see #setHorizontalAccuracy(int) */ + @AccuracyRequirement public int getHorizontalAccuracy() { return mHorizontalAccuracy; } /** - * Indicates the desired vertical accuracy (altitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired vertical accuracy (altitude). Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setVerticalAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mVerticalAccuracy = accuracy; + public void setVerticalAccuracy(@AccuracyRequirement int accuracy) { + mVerticalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired vertical accuracy (altitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * + * @see #setVerticalAccuracy(int) */ + @AccuracyRequirement public int getVerticalAccuracy() { return mVerticalAccuracy; } /** - * Indicates the desired speed accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired speed accuracy. Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setSpeedAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mSpeedAccuracy = accuracy; + public void setSpeedAccuracy(@AccuracyRequirement int accuracy) { + mSpeedAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_HIGH, + "accuracy"); } /** - * Returns a constant indicating the desired speed accuracy - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * Returns a constant indicating the desired speed accuracy. + * + * @see #setSpeedAccuracy(int) */ + @AccuracyRequirement public int getSpeedAccuracy() { return mSpeedAccuracy; } /** - * Indicates the desired bearing accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired bearing accuracy. Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setBearingAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mBearingAccuracy = accuracy; + public void setBearingAccuracy(@AccuracyRequirement int accuracy) { + mBearingAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired bearing accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * + * @see #setBearingAccuracy(int) */ + @AccuracyRequirement public int getBearingAccuracy() { return mBearingAccuracy; } /** - * Indicates the desired accuracy for latitude and longitude. Accuracy - * may be {@link #ACCURACY_FINE} if desired location - * is fine, else it can be {@link #ACCURACY_COARSE}. - * More accurate location may consume more power and may take longer. + * Indicates the desired accuracy for latitude and longitude. Accuracy may be + * {@link #ACCURACY_FINE} or {@link #ACCURACY_COARSE}. More accurate location may consume more + * power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - if (accuracy == ACCURACY_FINE) { - mHorizontalAccuracy = ACCURACY_HIGH; - } else { - mHorizontalAccuracy = ACCURACY_LOW; + public void setAccuracy(@LocationAccuracyRequirement int accuracy) { + Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_COARSE, "accuracy"); + switch (accuracy) { + case NO_REQUIREMENT: + setHorizontalAccuracy(NO_REQUIREMENT); + break; + case ACCURACY_FINE: + setHorizontalAccuracy(ACCURACY_HIGH); + break; + case ACCURACY_COARSE: + setHorizontalAccuracy(ACCURACY_LOW); + break; } } /** - * Returns a constant indicating desired accuracy of location - * Accuracy may be {@link #ACCURACY_FINE} if desired location - * is fine, else it can be {@link #ACCURACY_COARSE}. + * Returns a constant indicating desired accuracy of location. + * + * @see #setAccuracy(int) */ + @LocationAccuracyRequirement public int getAccuracy() { if (mHorizontalAccuracy >= ACCURACY_HIGH) { return ACCURACY_FINE; @@ -243,21 +266,20 @@ public class Criteria implements Parcelable { } /** - * Indicates the desired maximum power level. The level parameter - * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or - * POWER_HIGH. + * Indicates the desired maximum power requirement. The power requirement parameter may be + * {@link #NO_REQUIREMENT}, {@link #POWER_LOW}, {@link #POWER_MEDIUM}, or {@link #POWER_HIGH}. */ - public void setPowerRequirement(int level) { - if (level < NO_REQUIREMENT || level > POWER_HIGH) { - throw new IllegalArgumentException("level=" + level); - } - mPowerRequirement = level; + public void setPowerRequirement(@PowerRequirement int powerRequirement) { + mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, NO_REQUIREMENT, + POWER_HIGH, "powerRequirement"); } /** - * Returns a constant indicating the desired power requirement. The - * returned + * Returns a constant indicating the desired maximum power requirement. + * + * @see #setPowerRequirement(int) */ + @PowerRequirement public int getPowerRequirement() { return mPowerRequirement; } @@ -277,8 +299,8 @@ public class Criteria implements Parcelable { } /** - * Indicates whether the provider must provide altitude information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide altitude information. Not all fixes are + * guaranteed to contain such information. */ public void setAltitudeRequired(boolean altitudeRequired) { mAltitudeRequired = altitudeRequired; @@ -286,15 +308,16 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide altitude information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setAltitudeRequired(boolean) */ public boolean isAltitudeRequired() { return mAltitudeRequired; } /** - * Indicates whether the provider must provide speed information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide speed information. Not all fixes are guaranteed + * to contain such information. */ public void setSpeedRequired(boolean speedRequired) { mSpeedRequired = speedRequired; @@ -302,15 +325,16 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide speed information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setSpeedRequired(boolean) */ public boolean isSpeedRequired() { return mSpeedRequired; } /** - * Indicates whether the provider must provide bearing information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide bearing information. Not all fixes are guaranteed + * to contain such information. */ public void setBearingRequired(boolean bearingRequired) { mBearingRequired = bearingRequired; @@ -318,34 +342,36 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide bearing information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setBearingRequired(boolean) */ public boolean isBearingRequired() { return mBearingRequired; } - public static final @android.annotation.NonNull Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public Criteria createFromParcel(Parcel in) { - Criteria c = new Criteria(); - c.mHorizontalAccuracy = in.readInt(); - c.mVerticalAccuracy = in.readInt(); - c.mSpeedAccuracy = in.readInt(); - c.mBearingAccuracy = in.readInt(); - c.mPowerRequirement = in.readInt(); - c.mAltitudeRequired = in.readInt() != 0; - c.mBearingRequired = in.readInt() != 0; - c.mSpeedRequired = in.readInt() != 0; - c.mCostAllowed = in.readInt() != 0; - return c; - } + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public Criteria createFromParcel(Parcel in) { + Criteria c = new Criteria(); + c.mHorizontalAccuracy = in.readInt(); + c.mVerticalAccuracy = in.readInt(); + c.mSpeedAccuracy = in.readInt(); + c.mBearingAccuracy = in.readInt(); + c.mPowerRequirement = in.readInt(); + c.mAltitudeRequired = in.readInt() != 0; + c.mBearingRequired = in.readInt() != 0; + c.mSpeedRequired = in.readInt() != 0; + c.mCostAllowed = in.readInt() != 0; + return c; + } - @Override - public Criteria[] newArray(int size) { - return new Criteria[size]; - } - }; + @Override + public Criteria[] newArray(int size) { + return new Criteria[size]; + } + }; @Override public int describeContents() { @@ -365,42 +391,57 @@ public class Criteria implements Parcelable { parcel.writeInt(mCostAllowed ? 1 : 0); } - private static String powerToString(int power) { - switch (power) { - case NO_REQUIREMENT: - return "NO_REQ"; - case POWER_LOW: - return "LOW"; - case POWER_MEDIUM: - return "MEDIUM"; - case POWER_HIGH: - return "HIGH"; - default: - return "???"; - } - } - - private static String accuracyToString(int accuracy) { - switch (accuracy) { - case NO_REQUIREMENT: - return "---"; - case ACCURACY_HIGH: - return "HIGH"; - case ACCURACY_MEDIUM: - return "MEDIUM"; - case ACCURACY_LOW: - return "LOW"; - default: - return "???"; - } - } - @Override public String toString() { StringBuilder s = new StringBuilder(); - s.append("Criteria[power=").append(powerToString(mPowerRequirement)); - s.append(" acc=").append(accuracyToString(mHorizontalAccuracy)); + s.append("Criteria["); + s.append("power=").append(requirementToString(mPowerRequirement)).append(", "); + s.append("accuracy=").append(requirementToString(mHorizontalAccuracy)); + if (mVerticalAccuracy != NO_REQUIREMENT) { + s.append(", verticalAccuracy=").append(requirementToString(mVerticalAccuracy)); + } + if (mSpeedAccuracy != NO_REQUIREMENT) { + s.append(", speedAccuracy=").append(requirementToString(mSpeedAccuracy)); + } + if (mBearingAccuracy != NO_REQUIREMENT) { + s.append(", bearingAccuracy=").append(requirementToString(mBearingAccuracy)); + } + if (mAltitudeRequired || mBearingRequired || mSpeedRequired) { + s.append(", required=["); + if (mAltitudeRequired) { + s.append("altitude, "); + } + if (mBearingRequired) { + s.append("bearing, "); + } + if (mSpeedRequired) { + s.append("speed, "); + } + s.setLength(s.length() - 2); + s.append("]"); + } + if (mCostAllowed) { + s.append(", costAllowed"); + } s.append(']'); return s.toString(); } + + private static String requirementToString(int power) { + switch (power) { + case NO_REQUIREMENT: + return "None"; + //case ACCURACY_LOW: + case POWER_LOW: + return "Low"; + //case ACCURACY_MEDIUM: + case POWER_MEDIUM: + return "Medium"; + //case ACCURACY_HIGH: + case POWER_HIGH: + return "High"; + default: + return "???"; + } + } } diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java index def96f0fb674b..68f9ec3c530b7 100644 --- a/location/java/com/android/internal/location/ProviderProperties.java +++ b/location/java/com/android/internal/location/ProviderProperties.java @@ -16,15 +16,36 @@ package com.android.internal.location; +import android.annotation.IntDef; +import android.location.Criteria; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A Parcelable containing (legacy) location provider properties. * This object is just used inside the framework and system services. + * * @hide */ public final class ProviderProperties implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Criteria.POWER_LOW, Criteria.POWER_MEDIUM, Criteria.POWER_HIGH}) + public @interface PowerRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Criteria.ACCURACY_FINE, Criteria.ACCURACY_COARSE}) + public @interface Accuracy { + } + /** * True if provider requires access to a * data network (e.g., the Internet), false otherwise. @@ -79,58 +100,58 @@ public final class ProviderProperties implements Parcelable { /** * Power requirement for this provider. - * - * @return the power requirement for this provider, as one of the - * constants Criteria.POWER_*. */ + @PowerRequirement public final int mPowerRequirement; /** * Constant describing the horizontal accuracy returned * by this provider. - * - * @return the horizontal accuracy for this provider, as one of the - * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE */ + @Accuracy public final int mAccuracy; - public ProviderProperties(boolean mRequiresNetwork, - boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost, - boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing, - int mPowerRequirement, int mAccuracy) { - this.mRequiresNetwork = mRequiresNetwork; - this.mRequiresSatellite = mRequiresSatellite; - this.mRequiresCell = mRequiresCell; - this.mHasMonetaryCost = mHasMonetaryCost; - this.mSupportsAltitude = mSupportsAltitude; - this.mSupportsSpeed = mSupportsSpeed; - this.mSupportsBearing = mSupportsBearing; - this.mPowerRequirement = mPowerRequirement; - this.mAccuracy = mAccuracy; + public ProviderProperties(boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, @PowerRequirement int powerRequirement, + @Accuracy int accuracy) { + mRequiresNetwork = requiresNetwork; + mRequiresSatellite = requiresSatellite; + mRequiresCell = requiresCell; + mHasMonetaryCost = hasMonetaryCost; + mSupportsAltitude = supportsAltitude; + mSupportsSpeed = supportsSpeed; + mSupportsBearing = supportsBearing; + mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, Criteria.POWER_LOW, + Criteria.POWER_HIGH, "powerRequirement"); + mAccuracy = Preconditions.checkArgumentInRange(accuracy, Criteria.ACCURACY_FINE, + Criteria.ACCURACY_COARSE, "accuracy"); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public ProviderProperties createFromParcel(Parcel in) { - boolean requiresNetwork = in.readInt() == 1; - boolean requiresSatellite = in.readInt() == 1; - boolean requiresCell = in.readInt() == 1; - boolean hasMonetaryCost = in.readInt() == 1; - boolean supportsAltitude = in.readInt() == 1; - boolean supportsSpeed = in.readInt() == 1; - boolean supportsBearing = in.readInt() == 1; - int powerRequirement = in.readInt(); - int accuracy = in.readInt(); - return new ProviderProperties(requiresNetwork, requiresSatellite, - requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing, - powerRequirement, accuracy); - } - @Override - public ProviderProperties[] newArray(int size) { - return new ProviderProperties[size]; - } - }; + @Override + public ProviderProperties createFromParcel(Parcel in) { + boolean requiresNetwork = in.readInt() == 1; + boolean requiresSatellite = in.readInt() == 1; + boolean requiresCell = in.readInt() == 1; + boolean hasMonetaryCost = in.readInt() == 1; + boolean supportsAltitude = in.readInt() == 1; + boolean supportsSpeed = in.readInt() == 1; + boolean supportsBearing = in.readInt() == 1; + int powerRequirement = in.readInt(); + int accuracy = in.readInt(); + return new ProviderProperties(requiresNetwork, requiresSatellite, + requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, + supportsBearing, + powerRequirement, accuracy); + } + + @Override + public ProviderProperties[] newArray(int size) { + return new ProviderProperties[size]; + } + }; @Override public int describeContents() { @@ -149,4 +170,67 @@ public final class ProviderProperties implements Parcelable { parcel.writeInt(mPowerRequirement); parcel.writeInt(mAccuracy); } + + @Override + public String toString() { + StringBuilder b = new StringBuilder("ProviderProperties["); + b.append("power=").append(powerToString(mPowerRequirement)).append(", "); + b.append("accuracy=").append(accuracyToString(mAccuracy)); + if (mRequiresNetwork || mRequiresSatellite || mRequiresCell) { + b.append(", requires="); + if (mRequiresNetwork) { + b.append("network,"); + } + if (mRequiresSatellite) { + b.append("satellite,"); + } + if (mRequiresCell) { + b.append("cell,"); + } + b.setLength(b.length() - 1); + } + if (mHasMonetaryCost) { + b.append(", hasMonetaryCost"); + } + if (mSupportsBearing || mSupportsSpeed || mSupportsAltitude) { + b.append(", supports=["); + if (mSupportsBearing) { + b.append("bearing, "); + } + if (mSupportsSpeed) { + b.append("speed, "); + } + if (mSupportsAltitude) { + b.append("altitude, "); + } + b.setLength(b.length() - 2); + b.append("]"); + } + b.append("]"); + return b.toString(); + } + + private static String powerToString(@PowerRequirement int power) { + switch (power) { + case Criteria.POWER_LOW: + return "Low"; + case Criteria.POWER_MEDIUM: + return "Medium"; + case Criteria.POWER_HIGH: + return "High"; + default: + return "???"; + } + } + + private static String accuracyToString(@Accuracy int accuracy) { + switch (accuracy) { + case Criteria.ACCURACY_COARSE: + return "Coarse"; + case Criteria.ACCURACY_FINE: + return "Fine"; + default: + return "???"; + } + } } diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index c23f49976799f..8d8df4533ebe0 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -20,33 +20,42 @@ import android.compat.annotation.UnsupportedAppUsage; import android.location.LocationRequest; import android.os.Parcel; import android.os.Parcelable; +import android.os.WorkSource; import android.util.TimeUtils; +import com.android.internal.util.Preconditions; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @hide */ public final class ProviderRequest implements Parcelable { + + public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(false, Long.MAX_VALUE, + false, false, + Collections.emptyList(), new WorkSource()); + /** Location reporting is requested (true) */ @UnsupportedAppUsage - public boolean reportLocation = false; + public final boolean reportLocation; /** The smallest requested interval */ @UnsupportedAppUsage - public long interval = Long.MAX_VALUE; + public final long interval; + + /** + * Whether provider shall make stronger than normal tradeoffs to substantially restrict power + * use. + */ + public final boolean lowPowerMode; /** * When this flag is true, providers 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 flag should only be used in event of an emergency. */ - public boolean locationSettingsIgnored = false; - - /** - * Whether provider shall make stronger than normal tradeoffs to substantially restrict power - * use. - */ - public boolean lowPowerMode = false; + public final boolean locationSettingsIgnored; /** * A more detailed set of requests. @@ -56,26 +65,37 @@ public final class ProviderRequest implements Parcelable { * low power fast interval request. */ @UnsupportedAppUsage - public final List locationRequests = new ArrayList<>(); + public final List locationRequests; - @UnsupportedAppUsage - public ProviderRequest() { + public final WorkSource workSource; + + private ProviderRequest(boolean reportLocation, long interval, boolean lowPowerMode, + boolean locationSettingsIgnored, List locationRequests, + WorkSource workSource) { + this.reportLocation = reportLocation; + this.interval = interval; + this.lowPowerMode = lowPowerMode; + this.locationSettingsIgnored = locationSettingsIgnored; + this.locationRequests = Preconditions.checkNotNull(locationRequests); + this.workSource = Preconditions.checkNotNull(workSource); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public ProviderRequest createFromParcel(Parcel in) { - ProviderRequest request = new ProviderRequest(); - request.reportLocation = in.readInt() == 1; - request.interval = in.readLong(); - request.lowPowerMode = in.readBoolean(); - request.locationSettingsIgnored = in.readBoolean(); + boolean reportLocation = in.readInt() == 1; + long interval = in.readLong(); + boolean lowPowerMode = in.readBoolean(); + boolean locationSettingsIgnored = in.readBoolean(); int count = in.readInt(); + ArrayList locationRequests = new ArrayList<>(count); for (int i = 0; i < count; i++) { - request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); + locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); } - return request; + WorkSource workSource = in.readParcelable(null); + return new ProviderRequest(reportLocation, interval, lowPowerMode, + locationSettingsIgnored, locationRequests, workSource); } @Override @@ -106,14 +126,13 @@ public final class ProviderRequest implements Parcelable { StringBuilder s = new StringBuilder(); s.append("ProviderRequest["); if (reportLocation) { - s.append("ON"); - s.append(" interval="); + s.append("interval="); TimeUtils.formatDuration(interval, s); if (lowPowerMode) { - s.append(" lowPowerMode"); + s.append(", lowPowerMode"); } if (locationSettingsIgnored) { - s.append(" locationSettingsIgnored"); + s.append(", locationSettingsIgnored"); } } else { s.append("OFF"); @@ -121,4 +140,67 @@ public final class ProviderRequest implements Parcelable { s.append(']'); return s.toString(); } + + /** + * A Builder for {@link ProviderRequest}s. + */ + public static class Builder { + private long mInterval = Long.MAX_VALUE; + private boolean mLowPowerMode; + private boolean mLocationSettingsIgnored; + private List mLocationRequests = Collections.emptyList(); + private WorkSource mWorkSource = new WorkSource(); + + public long getInterval() { + return mInterval; + } + + public void setInterval(long interval) { + this.mInterval = interval; + } + + public boolean isLowPowerMode() { + return mLowPowerMode; + } + + public void setLowPowerMode(boolean lowPowerMode) { + this.mLowPowerMode = lowPowerMode; + } + + public boolean isLocationSettingsIgnored() { + return mLocationSettingsIgnored; + } + + public void setLocationSettingsIgnored(boolean locationSettingsIgnored) { + this.mLocationSettingsIgnored = locationSettingsIgnored; + } + + public List getLocationRequests() { + return mLocationRequests; + } + + public void setLocationRequests(List locationRequests) { + this.mLocationRequests = Preconditions.checkNotNull(locationRequests); + } + + public WorkSource getWorkSource() { + return mWorkSource; + } + + public void setWorkSource(WorkSource workSource) { + mWorkSource = Preconditions.checkNotNull(workSource); + } + + /** + * Builds a ProviderRequest object with the set information. + */ + public ProviderRequest build() { + if (mInterval == Long.MAX_VALUE) { + return EMPTY_REQUEST; + } else { + return new ProviderRequest(true, mInterval, mLowPowerMode, + mLocationSettingsIgnored, mLocationRequests, mWorkSource); + } + } + } } diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java index bbcfdc63f3f17..32cdc41472c94 100644 --- a/services/core/java/com/android/server/GnssManagerService.java +++ b/services/core/java/com/android/server/GnssManagerService.java @@ -47,7 +47,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocationManagerServiceUtils.LinkedListener; import com.android.server.LocationManagerServiceUtils.LinkedListenerBase; -import com.android.server.location.AbstractLocationProvider; import com.android.server.location.CallerIdentity; import com.android.server.location.GnssBatchingProvider; import com.android.server.location.GnssCapabilitiesProvider; @@ -116,11 +115,9 @@ public class GnssManagerService { private final Handler mHandler; public GnssManagerService(LocationManagerService locationManagerService, - Context context, - AbstractLocationProvider.LocationProviderManager gnssProviderManager, - LocationUsageLogger locationUsageLogger) { - this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager, - FgThread.getHandler().getLooper()), locationUsageLogger); + Context context, LocationUsageLogger locationUsageLogger) { + this(locationManagerService, context, + new GnssLocationProvider(context, FgThread.getHandler()), locationUsageLogger); } // Can use this constructor to inject GnssLocationProvider for testing diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index c5f1923b0b984..be537ac052a68 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -23,8 +23,6 @@ import static android.location.LocationManager.NETWORK_PROVIDER; import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.os.PowerManager.locationPowerSaveModeToString; -import static com.android.internal.util.Preconditions.checkState; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -74,7 +72,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; -import android.provider.Settings; import android.stats.location.LocationStatsEnums; import android.text.TextUtils; import android.util.EventLog; @@ -92,6 +89,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.location.AbstractLocationProvider; +import com.android.server.location.AbstractLocationProvider.State; import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.CallerIdentity; import com.android.server.location.GeocoderProxy; @@ -105,6 +103,7 @@ import com.android.server.location.LocationRequestStatistics.PackageStatistics; import com.android.server.location.LocationSettingsStore; import com.android.server.location.LocationUsageLogger; import com.android.server.location.MockProvider; +import com.android.server.location.MockableLocationProvider; import com.android.server.location.PassiveProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -121,6 +120,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; /** @@ -196,6 +197,8 @@ public class LocationManagerService extends ILocationManager.Stub { private final LocationSettingsStore mSettingsStore; private final LocationUsageLogger mLocationUsageLogger; + private final PassiveLocationProviderManager mPassiveManager; + private AppOpsManager mAppOps; private PackageManager mPackageManager; private PowerManager mPowerManager; @@ -205,21 +208,17 @@ public class LocationManagerService extends ILocationManager.Stub { private GeofenceManager mGeofenceManager; private LocationFudger mLocationFudger; private GeocoderProxy mGeocodeProvider; - @Nullable - private GnssManagerService mGnssManagerService; - private PassiveProvider mPassiveProvider; // track passive provider for special cases + @Nullable private GnssManagerService mGnssManagerService; + @GuardedBy("mLock") private String mExtraLocationControllerPackage; + @GuardedBy("mLock") private boolean mExtraLocationControllerPackageEnabled; - // list of currently active providers - @GuardedBy("mLock") - private final ArrayList mProviders = new ArrayList<>(); - - // list of non-mock providers, so that when mock providers replace real providers, they can be - // later re-replaced - @GuardedBy("mLock") - private final ArrayList mRealProviders = new ArrayList<>(); + // @GuardedBy("mLock") + // hold lock for write or to prevent write, no lock for read + private final CopyOnWriteArrayList mProviderManagers = + new CopyOnWriteArrayList<>(); @GuardedBy("mLock") private final HashMap mReceivers = new HashMap<>(); @@ -238,9 +237,9 @@ public class LocationManagerService extends ILocationManager.Stub { private final HashMap mLastLocationCoarseInterval = new HashMap<>(); - // current active user on the device - other users are denied location data - private int mCurrentUserId = UserHandle.USER_SYSTEM; - private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM}; + // current active user on the device + private int mCurrentUserId; + private int[] mCurrentUserProfiles; @GuardedBy("mLock") @PowerManager.LocationPowerSaveMode @@ -252,6 +251,17 @@ public class LocationManagerService extends ILocationManager.Stub { mSettingsStore = new LocationSettingsStore(mContext, mHandler); mLocationUsageLogger = new LocationUsageLogger(); + mCurrentUserId = UserHandle.USER_NULL; + mCurrentUserProfiles = new int[]{UserHandle.USER_NULL}; + + // set up passive provider - we do this early because it has no dependencies on system + // services or external code that isn't ready yet, and because this allows the variable to + // be final. other more complex providers are initialized later, when system services are + // ready + mPassiveManager = new PassiveLocationProviderManager(); + mProviderManagers.add(mPassiveManager); + mPassiveManager.setRealProvider(new PassiveProvider(mContext)); + // Let the package manager query which are the default location // providers as they get certain permissions granted by default. PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService( @@ -415,15 +425,15 @@ public class LocationManagerService extends ILocationManager.Stub { for (Receiver receiver : mReceivers.values()) { receiver.updateMonitoring(true); } - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onPermissionsChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @@ -442,16 +452,16 @@ public class LocationManagerService extends ILocationManager.Stub { mBatterySaverMode = newLocationMode; - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onScreenStateChangedLocked() { if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } } @@ -466,8 +476,8 @@ public class LocationManagerService extends ILocationManager.Stub { intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId)); mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - for (LocationProviderManager p : mProviders) { - p.onUseableChangedLocked(userId); + for (LocationProviderManager manager : mProviderManagers) { + manager.onUseableChangedLocked(userId); } } @@ -521,22 +531,22 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private void onBackgroundThrottleIntervalChangedLocked() { - for (LocationProviderManager provider : mProviders) { - applyRequirementsLocked(provider); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onBackgroundThrottleWhitelistChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("lock") private void onIgnoreSettingsWhitelistChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @@ -623,22 +633,11 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private void initializeProvidersLocked() { - // create a passive location provider, which is always enabled - LocationProviderManager passiveProviderManager = new LocationProviderManager( - PASSIVE_PROVIDER); - addProviderLocked(passiveProviderManager); - mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager); - passiveProviderManager.attachLocked(mPassiveProvider); - if (GnssManagerService.isGnssSupported()) { - // Create a gps location provider manager - LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER); - mRealProviders.add(gnssProviderManager); - addProviderLocked(gnssProviderManager); - - mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager, - mLocationUsageLogger); - gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider()); + mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger); + LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER); + mProviderManagers.add(gnssManager); + gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider()); } /* @@ -662,37 +661,31 @@ public class LocationManagerService extends ILocationManager.Stub { ensureFallbackFusedProviderPresentLocked(pkgs); - // bind to network provider - LocationProviderManager networkProviderManager = new LocationProviderManager( - NETWORK_PROVIDER); LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( mContext, - networkProviderManager, NETWORK_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableNetworkLocationOverlay, com.android.internal.R.string.config_networkLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (networkProvider != null) { - mRealProviders.add(networkProviderManager); - addProviderLocked(networkProviderManager); - networkProviderManager.attachLocked(networkProvider); + LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER); + mProviderManagers.add(networkManager); + networkManager.setRealProvider(networkProvider); } else { Slog.w(TAG, "no network location provider found"); } // bind to fused provider - LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER); LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind( mContext, - fusedProviderManager, FUSED_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableFusedLocationOverlay, com.android.internal.R.string.config_fusedLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (fusedProvider != null) { - mRealProviders.add(fusedProviderManager); - addProviderLocked(fusedProviderManager); - fusedProviderManager.attachLocked(fusedProvider); + LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER); + mProviderManagers.add(fusedManager); + fusedManager.setRealProvider(fusedProvider); } else { Slog.e(TAG, "no fused location provider found", new IllegalStateException("Location service needs a fused location provider")); @@ -754,10 +747,7 @@ public class LocationManagerService extends ILocationManager.Stub { Boolean.parseBoolean(fragments[7]) /* supportsBearing */, Integer.parseInt(fragments[8]) /* powerRequirement */, Integer.parseInt(fragments[9]) /* accuracy */); - LocationProviderManager testProviderManager = new LocationProviderManager(name); - addProviderLocked(testProviderManager); - testProviderManager.attachLocked( - new MockProvider(mContext, testProviderManager, properties)); + addTestProvider(name, properties, mContext.getOpPackageName()); } } @@ -771,231 +761,202 @@ public class LocationManagerService extends ILocationManager.Stub { Log.d(TAG, "foreground user is changing to " + userId); } - int oldUserId = userId; + int oldUserId = mCurrentUserId; mCurrentUserId = userId; onUserProfilesChangedLocked(); // let providers know the current user has changed - for (LocationProviderManager p : mProviders) { - p.onUseableChangedLocked(oldUserId); - p.onUseableChangedLocked(mCurrentUserId); + for (LocationProviderManager manager : mProviderManagers) { + // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility + mSettingsStore.setLocationProviderAllowed(manager.getName(), + manager.isUseable(mCurrentUserId), mCurrentUserId); + + manager.onUseableChangedLocked(oldUserId); + manager.onUseableChangedLocked(mCurrentUserId); } } /** * Location provider manager, manages a LocationProvider. */ - class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager { + class LocationProviderManager implements MockableLocationProvider.Listener { private final String mName; - // remember to clear binder identity before invoking any provider operation - @GuardedBy("mLock") - @Nullable - protected AbstractLocationProvider mProvider; + // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary + protected final MockableLocationProvider mProvider; @GuardedBy("mLock") - private SparseArray mUseable; // combined state for each user id - @GuardedBy("mLock") - private boolean mEnabled; // state of provider - - @GuardedBy("mLock") - @Nullable - private ProviderProperties mProperties; + private final SparseArray mUseable; // combined state for each user id private LocationProviderManager(String name) { mName = name; - - mProvider = null; mUseable = new SparseArray<>(1); - mEnabled = false; - mProperties = null; - // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-" + mName, - mCurrentUserId); - } - - @GuardedBy("mLock") - public void attachLocked(AbstractLocationProvider provider) { - Objects.requireNonNull(provider); - checkState(mProvider == null); - - if (D) { - Log.d(TAG, mName + " provider attached"); - } - - mProvider = provider; - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - onUseableChangedLocked(mCurrentUserId); + // initialize last since this lets our reference escape + mProvider = new MockableLocationProvider(mContext, mLock, this); } public String getName() { return mName; } - @GuardedBy("mLock") - public List getPackagesLocked() { - if (mProvider == null) { - return Collections.emptyList(); - } else { - // safe to not clear binder context since this doesn't call into the real provider - return mProvider.getProviderPackages(); - } + public boolean hasProvider() { + return mProvider.getProvider() != null; } - public boolean isMock() { - return false; + public void setRealProvider(AbstractLocationProvider provider) { + mProvider.setRealProvider(provider); } - @GuardedBy("mLock") - public boolean isPassiveLocked() { - return mProvider == mPassiveProvider; + public void setMockProvider(@Nullable MockProvider provider) { + mProvider.setMockProvider(provider); + } + + public Set getPackages() { + return mProvider.getState().providerPackageNames; } - @GuardedBy("mLock") @Nullable - public ProviderProperties getPropertiesLocked() { - return mProperties; + public ProviderProperties getProperties() { + return mProvider.getState().properties; } - public void setRequest(ProviderRequest request, WorkSource workSource) { - // move calls going to providers onto a different thread to avoid deadlock - mHandler.post(() -> { - synchronized (mLock) { - if (mProvider != null) { - mProvider.onSetRequest(request, workSource); - } + public void setMockProviderEnabled(boolean enabled) { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); } - }); + + mProvider.setMockProviderEnabled(enabled); + } } - public void sendExtraCommand(String command, Bundle extras) { - int uid = Binder.getCallingUid(); - int pid = Binder.getCallingPid(); - - // move calls going to providers onto a different thread to avoid deadlock - mHandler.post(() -> { - synchronized (mLock) { - if (mProvider != null) { - mProvider.onSendExtraCommand(uid, pid, command, extras); - } + public void setMockProviderLocation(Location location) { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); } - }); + + String locationProvider = location.getProvider(); + if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) { + // The location has an explicit provider that is different from the mock + // provider name. The caller may be trying to fool us via b/33091107. + EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), + mName + "!=" + locationProvider); + } + + mProvider.setMockProviderLocation(location); + } } - @GuardedBy("mLock") - public void dumpLocked(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { - pw.print(mName + " provider"); - if (isMock()) { - pw.print(" [mock]"); - } - pw.println(":"); - - pw.increaseIndent(); - - pw.println("useable=" + isUseableLocked(mCurrentUserId)); - if (!isUseableLocked(mCurrentUserId)) { - pw.println("attached=" + (mProvider != null)); - pw.println("enabled=" + mEnabled); - } - - pw.println("properties=" + mProperties); - - if (mProvider != null) { - // in order to be consistent with other provider APIs, this should be run on the - // location thread... but this likely isn't worth it just for dumping info. - long identity = Binder.clearCallingIdentity(); - try { - mProvider.dump(fd, pw, args); - } finally { - Binder.restoreCallingIdentity(identity); + public List getMockProviderRequests() { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); } + + return mProvider.getCurrentRequest().locationRequests; } + } + + public void setRequest(ProviderRequest request) { + mProvider.setRequest(request); + } + + public void sendExtraCommand(int uid, int pid, String command, Bundle extras) { + mProvider.sendExtraCommand(uid, pid, command, extras); + } + + public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { + synchronized (mLock) { + pw.print(mName + " provider"); + if (mProvider.isMock()) { + pw.print(" [mock]"); + } + pw.println(":"); + + pw.increaseIndent(); + + pw.println("useable=" + isUseable(mCurrentUserId)); + if (!isUseable(mCurrentUserId)) { + pw.println("enabled=" + mProvider.getState().enabled); + } + + pw.println("properties=" + mProvider.getState().properties); + } + + mProvider.dump(fd, pw, args); pw.decreaseIndent(); } + @GuardedBy("mLock") @Override public void onReportLocation(Location location) { - // likelihood of a 0,0 bug is far greater than this being a valid location - if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) { - Slog.w(TAG, "blocking 0,0 location from " + mName + " provider"); - return; + // don't validate mock locations + if (!location.isFromMockProvider()) { + if (location.getLatitude() == 0 && location.getLongitude() == 0) { + Slog.w(TAG, "blocking 0,0 location from " + mName + " provider"); + return; + } } - synchronized (mLock) { - handleLocationChangedLocked(location, this); - } + handleLocationChangedLocked(location, this); } + @GuardedBy("mLock") @Override public void onReportLocation(List locations) { if (mGnssManagerService == null) { return; } - synchronized (mLock) { - LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER); - if (gpsProvider == null || !gpsProvider.isUseableLocked()) { - Slog.w(TAG, "reportLocationBatch() called without user permission"); - return; - } - mGnssManagerService.onReportLocation(locations); + if (!GPS_PROVIDER.equals(mName) || !isUseable()) { + Slog.w(TAG, "reportLocationBatch() called without user permission"); + return; } + + mGnssManagerService.onReportLocation(locations); } + @GuardedBy("mLock") @Override - public void onSetEnabled(boolean enabled) { - synchronized (mLock) { - if (enabled == mEnabled) { - return; - } - - if (D) { - Log.d(TAG, mName + " provider enabled is now " + mEnabled); - } - - mEnabled = enabled; - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users + public void onStateChanged(State oldState, State newState) { + if (oldState.enabled != newState.enabled) { + // it would be more correct to call this for all users, but we know this can + // only affect the current user since providers are disabled for non-current + // users onUseableChangedLocked(mCurrentUserId); } } - @Override - public void onSetProperties(ProviderProperties properties) { + @GuardedBy("mLock") + public boolean isUseable() { + return isUseable(mCurrentUserId); + } + + @GuardedBy("mLock") + public boolean isUseable(int userId) { synchronized (mLock) { - mProperties = properties; + return mUseable.get(userId, Boolean.FALSE); } } - @GuardedBy("mLock") - public boolean isUseableLocked() { - return isUseableLocked(mCurrentUserId); - } - - @GuardedBy("mLock") - public boolean isUseableLocked(int userId) { - return mUseable.get(userId, Boolean.FALSE); - } - @GuardedBy("mLock") public void onUseableChangedLocked(int userId) { + if (userId == UserHandle.USER_NULL) { + // only used during initialization - we don't care about the null user + return; + } + // if any property that contributes to "useability" here changes state, it MUST result // in a direct or indrect call to onUseableChangedLocked. this allows the provider to // guarantee that it will always eventually reach the correct state. - boolean useable = mProvider != null && mProviders.contains(this) - && isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId) - && mEnabled; + boolean useable = isCurrentProfileLocked(userId) + && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled; - if (useable == isUseableLocked(userId)) { + if (useable == isUseable(userId)) { return; } mUseable.put(userId, useable); @@ -1007,11 +968,7 @@ public class LocationManagerService extends ILocationManager.Stub { // fused and passive provider never get public updates for legacy reasons if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) { // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - (useable ? "+" : "-") + mName, - userId); + mSettingsStore.setLocationProviderAllowed(mName, useable, userId); Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION); intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName); @@ -1031,53 +988,38 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private class MockLocationProvider extends LocationProviderManager { + class PassiveLocationProviderManager extends LocationProviderManager { - private ProviderRequest mCurrentRequest; - - private MockLocationProvider(String name) { - super(name); + private PassiveLocationProviderManager() { + super(PASSIVE_PROVIDER); } @Override - public void attachLocked(AbstractLocationProvider provider) { - checkState(provider instanceof MockProvider); - super.attachLocked(provider); + public void setRealProvider(AbstractLocationProvider provider) { + Preconditions.checkArgument(provider instanceof PassiveProvider); + super.setRealProvider(provider); } - public boolean isMock() { - return true; + @Override + public void setMockProvider(@Nullable MockProvider provider) { + if (provider != null) { + throw new IllegalArgumentException("Cannot mock the passive provider"); + } } - @GuardedBy("mLock") - public void setEnabledLocked(boolean enabled) { - if (mProvider != null) { + public void updateLocation(Location location) { + synchronized (mLock) { + PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider(); + Preconditions.checkState(passiveProvider != null); + long identity = Binder.clearCallingIdentity(); try { - ((MockProvider) mProvider).setEnabled(enabled); + passiveProvider.updateLocation(location); } finally { Binder.restoreCallingIdentity(identity); } } } - - @GuardedBy("mLock") - public void setLocationLocked(Location location) { - if (mProvider != null) { - long identity = Binder.clearCallingIdentity(); - try { - ((MockProvider) mProvider).setLocation(location); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - @Override - public void setRequest(ProviderRequest request, WorkSource workSource) { - super.setRequest(request, workSource); - mCurrentRequest = request; - } } /** @@ -1181,17 +1123,17 @@ public class LocationManagerService extends ILocationManager.Stub { // See if receiver has any enabled update records. Also note if any update records // are high power (has a high power provider with an interval under a threshold). for (UpdateRecord updateRecord : mUpdateRecords.values()) { - LocationProviderManager provider = getLocationProviderLocked( + LocationProviderManager manager = getLocationProviderManager( updateRecord.mProvider); - if (provider == null) { + if (manager == null) { continue; } - if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) { + if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) { continue; } requestingLocation = true; - ProviderProperties properties = provider.getPropertiesLocked(); + ProviderProperties properties = manager.getProperties(); if (properties != null && properties.mPowerRequirement == Criteria.POWER_HIGH && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { @@ -1432,7 +1374,7 @@ public class LocationManagerService extends ILocationManager.Stub { String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback( + return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback( callback, packageName, featureId, listenerIdentifier); } @@ -1443,7 +1385,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) { - return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos, + return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos, wakeOnFifoFull, packageName); } @@ -1454,35 +1396,14 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean stopGnssBatch() { - return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch(); + return mGnssManagerService != null && mGnssManagerService.stopGnssBatch(); } - @GuardedBy("mLock") - private void addProviderLocked(LocationProviderManager provider) { - Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null); - - mProviders.add(provider); - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - provider.onUseableChangedLocked(mCurrentUserId); - } - - @GuardedBy("mLock") - private void removeProviderLocked(LocationProviderManager provider) { - if (mProviders.remove(provider)) { - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - provider.onUseableChangedLocked(mCurrentUserId); - } - } - - @GuardedBy("mLock") @Nullable - private LocationProviderManager getLocationProviderLocked(String providerName) { - for (LocationProviderManager provider : mProviders) { - if (providerName.equals(provider.getName())) { - return provider; + private LocationProviderManager getLocationProviderManager(String providerName) { + for (LocationProviderManager manager : mProviderManagers) { + if (providerName.equals(manager.getName())) { + return manager; } } @@ -1531,12 +1452,12 @@ public class LocationManagerService extends ILocationManager.Stub { // network and fused providers are ok with COARSE or FINE return RESOLUTION_LEVEL_COARSE; } else { - for (LocationProviderManager lp : mProviders) { + for (LocationProviderManager lp : mProviderManagers) { if (!lp.getName().equals(provider)) { continue; } - ProviderProperties properties = lp.getPropertiesLocked(); + ProviderProperties properties = lp.getProperties(); if (properties != null) { if (properties.mRequiresSatellite) { // provider requiring satellites require FINE permission @@ -1587,11 +1508,9 @@ public class LocationManagerService extends ILocationManager.Stub { case RESOLUTION_LEVEL_COARSE: return AppOpsManager.OPSTR_COARSE_LOCATION; case RESOLUTION_LEVEL_FINE: - return AppOpsManager.OPSTR_FINE_LOCATION; + // fall through case RESOLUTION_LEVEL_NONE: - // The client is not allowed to get any location, so both FINE and COARSE ops will - // be denied. Pick the most restrictive one to be safe. - return AppOpsManager.OPSTR_FINE_LOCATION; + // fall through default: // Use the most restrictive ops if not sure. return AppOpsManager.OPSTR_FINE_LOCATION; @@ -1629,17 +1548,14 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public List getAllProviders() { - synchronized (mLock) { - ArrayList providers = new ArrayList<>(mProviders.size()); - for (LocationProviderManager provider : mProviders) { - String name = provider.getName(); - if (FUSED_PROVIDER.equals(name)) { - continue; - } - providers.add(name); + ArrayList providers = new ArrayList<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + if (FUSED_PROVIDER.equals(manager.getName())) { + continue; } - return providers; + providers.add(manager.getName()); } + return providers; } /** @@ -1651,21 +1567,21 @@ public class LocationManagerService extends ILocationManager.Stub { public List getProviders(Criteria criteria, boolean enabledOnly) { int allowedResolutionLevel = getCallerAllowedResolutionLevel(); synchronized (mLock) { - ArrayList providers = new ArrayList<>(mProviders.size()); - for (LocationProviderManager provider : mProviders) { - String name = provider.getName(); + ArrayList providers = new ArrayList<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + String name = manager.getName(); if (FUSED_PROVIDER.equals(name)) { continue; } if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) { continue; } - if (enabledOnly && !provider.isUseableLocked()) { + if (enabledOnly && !manager.isUseable()) { continue; } if (criteria != null && !android.location.LocationProvider.propertiesMeetCriteria( - name, provider.getPropertiesLocked(), criteria)) { + name, manager.getProperties(), criteria)) { continue; } providers.add(name); @@ -1702,12 +1618,12 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void updateProviderUseableLocked(LocationProviderManager provider) { - boolean useable = provider.isUseableLocked(); + private void updateProviderUseableLocked(LocationProviderManager manager) { + boolean useable = manager.isUseable(); ArrayList deadReceivers = null; - ArrayList records = mRecordsByProvider.get(provider.getName()); + ArrayList records = mRecordsByProvider.get(manager.getName()); if (records != null) { for (UpdateRecord record : records) { if (!isCurrentProfileLocked( @@ -1721,7 +1637,7 @@ public class LocationManagerService extends ILocationManager.Stub { } // Sends a notification message to the receiver - if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) { + if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) { if (deadReceivers == null) { deadReceivers = new ArrayList<>(); } @@ -1736,26 +1652,25 @@ public class LocationManagerService extends ILocationManager.Stub { } } - applyRequirementsLocked(provider); + applyRequirementsLocked(manager); } @GuardedBy("mLock") private void applyRequirementsLocked(String providerName) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider != null) { - applyRequirementsLocked(provider); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") - private void applyRequirementsLocked(LocationProviderManager provider) { - ArrayList records = mRecordsByProvider.get(provider.getName()); - WorkSource worksource = new WorkSource(); - ProviderRequest providerRequest = new ProviderRequest(); + private void applyRequirementsLocked(LocationProviderManager manager) { + ArrayList records = mRecordsByProvider.get(manager.getName()); + ProviderRequest.Builder providerRequest = new ProviderRequest.Builder(); // if provider is not active, it should not respond to requests - if (mProviders.contains(provider) && records != null && !records.isEmpty()) { + if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) { long backgroundThrottleInterval; long identity = Binder.clearCallingIdentity(); @@ -1765,6 +1680,8 @@ public class LocationManagerService extends ILocationManager.Stub { Binder.restoreCallingIdentity(identity); } + ArrayList requests = new ArrayList<>(records.size()); + final boolean isForegroundOnlyMode = mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY; final boolean shouldThrottleRequests = @@ -1772,7 +1689,7 @@ public class LocationManagerService extends ILocationManager.Stub { == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF && !mPowerManager.isInteractive(); // initialize the low power mode to true and set to false if any of the records requires - providerRequest.lowPowerMode = true; + providerRequest.setLowPowerMode(true); for (UpdateRecord record : records) { if (!isCurrentProfileLocked( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { @@ -1787,10 +1704,10 @@ public class LocationManagerService extends ILocationManager.Stub { } final boolean isBatterySaverDisablingLocation = shouldThrottleRequests || (isForegroundOnlyMode && !record.mIsForegroundUid); - if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) { + if (!manager.isUseable() || isBatterySaverDisablingLocation) { if (isSettingsExemptLocked(record)) { - providerRequest.locationSettingsIgnored = true; - providerRequest.lowPowerMode = false; + providerRequest.setLocationSettingsIgnored(true); + providerRequest.setLowPowerMode(false); } else { continue; } @@ -1801,7 +1718,7 @@ public class LocationManagerService extends ILocationManager.Stub { // if we're forcing location, don't apply any throttling - if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked( + if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked( record.mReceiver.mCallerIdentity)) { if (!record.mIsForegroundUid) { interval = Math.max(interval, backgroundThrottleInterval); @@ -1813,23 +1730,25 @@ public class LocationManagerService extends ILocationManager.Stub { } record.mRequest = locationRequest; - providerRequest.locationRequests.add(locationRequest); + requests.add(locationRequest); if (!locationRequest.isLowPowerMode()) { - providerRequest.lowPowerMode = false; + providerRequest.setLowPowerMode(false); } - if (interval < providerRequest.interval) { - providerRequest.reportLocation = true; - providerRequest.interval = interval; + if (interval < providerRequest.getInterval()) { + providerRequest.setInterval(interval); } } - if (providerRequest.reportLocation) { + providerRequest.setLocationRequests(requests); + + if (providerRequest.getInterval() < Long.MAX_VALUE) { // calculate who to blame for power // This is somewhat arbitrary. We pick a threshold interval // that is slightly higher that the minimum interval, and // spread the blame across all applications with a request // under that threshold. - long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; + // TODO: overflow + long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2; for (UpdateRecord record : records) { if (isCurrentProfileLocked( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { @@ -1837,18 +1756,18 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't assign battery blame for update records whose // client has no permission to receive location data. - if (!providerRequest.locationRequests.contains(locationRequest)) { + if (!providerRequest.getLocationRequests().contains(locationRequest)) { continue; } if (locationRequest.getInterval() <= thresholdInterval) { if (record.mReceiver.mWorkSource != null && isValidWorkSource(record.mReceiver.mWorkSource)) { - worksource.add(record.mReceiver.mWorkSource); + providerRequest.getWorkSource().add(record.mReceiver.mWorkSource); } else { // Assign blame to caller if there's no WorkSource associated with // the request or if it's invalid. - worksource.add( + providerRequest.getWorkSource().add( record.mReceiver.mCallerIdentity.mUid, record.mReceiver.mCallerIdentity.mPackageName); } @@ -1858,7 +1777,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } - provider.setRequest(providerRequest, worksource); + manager.setRequest(providerRequest.build()); } /** @@ -2198,8 +2117,8 @@ public class LocationManagerService extends ILocationManager.Stub { throw new IllegalArgumentException("provider name must not be null"); } - LocationProviderManager provider = getLocationProviderLocked(name); - if (provider == null) { + LocationProviderManager manager = getLocationProviderManager(name); + if (manager == null) { throw new IllegalArgumentException("provider doesn't exist: " + name); } @@ -2217,7 +2136,7 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) { + if (!manager.isUseable() && !isSettingsExemptLocked(record)) { // Notify the listener that updates are currently disabled - but only if the request // does not ignore location settings receiver.callProviderEnabledLocked(name, false); @@ -2320,8 +2239,8 @@ public class LocationManagerService extends ILocationManager.Stub { // or use the fused provider String name = request.getProvider(); if (name == null) name = LocationManager.FUSED_PROVIDER; - LocationProviderManager provider = getLocationProviderLocked(name); - if (provider == null) return null; + LocationProviderManager manager = getLocationProviderManager(name); + if (manager == null) return null; // only the current user or location providers may get location this way if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage( @@ -2329,7 +2248,7 @@ public class LocationManagerService extends ILocationManager.Stub { return null; } - if (!provider.isUseableLocked()) { + if (!manager.isUseable()) { return null; } @@ -2450,19 +2369,19 @@ public class LocationManagerService extends ILocationManager.Stub { "Access Fine Location permission not granted to inject Location"); synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(location.getProvider()); - if (provider == null || !provider.isUseableLocked()) { + LocationProviderManager manager = getLocationProviderManager(location.getProvider()); + if (manager == null || !manager.isUseable()) { return false; } // NOTE: If last location is already available, location is not injected. If // provider's normal source (like a GPS chipset) have already provided an output // there is no need to inject this location. - if (mLastLocation.get(provider.getName()) != null) { + if (mLastLocation.get(manager.getName()) != null) { return false; } - updateLastLocationLocked(location, provider.getName()); + updateLastLocationLocked(location, manager.getName()); return true; } } @@ -2511,7 +2430,7 @@ public class LocationManagerService extends ILocationManager.Stub { packageName, request, /* hasListener= */ false, - intent != null, + true, geofence, mActivityManager.getPackageImportance(packageName)); } @@ -2542,7 +2461,7 @@ public class LocationManagerService extends ILocationManager.Stub { packageName, /* LocationRequest= */ null, /* hasListener= */ false, - intent != null, + true, geofence, mActivityManager.getPackageImportance(packageName)); } @@ -2555,7 +2474,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName, String featureId) { - return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback( + return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback( listener, packageName, featureId); } @@ -2569,9 +2488,8 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false - : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId, - listenerIdentifier); + return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener( + listener, packageName, featureId, listenerIdentifier); } @Override @@ -2586,8 +2504,8 @@ public class LocationManagerService extends ILocationManager.Stub { public void injectGnssMeasurementCorrections( GnssMeasurementCorrections measurementCorrections, String packageName) { if (mGnssManagerService != null) { - mGnssManagerService.injectGnssMeasurementCorrections( - measurementCorrections, packageName); + mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections, + packageName); } } @@ -2602,9 +2520,8 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false - : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName, - featureId, listenerIdentifier); + return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener( + listener, packageName, featureId, listenerIdentifier); } @Override @@ -2634,9 +2551,10 @@ public class LocationManagerService extends ILocationManager.Stub { LocationStatsEnums.API_SEND_EXTRA_COMMAND, providerName); - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider != null) { - provider.sendExtraCommand(command, extras); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command, + extras); } mLocationUsageLogger.logLocationApiUsage( @@ -2650,43 +2568,37 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean sendNiResponse(int notifId, int userResponse) { - return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId, + return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId, userResponse); } @Override public ProviderProperties getProviderProperties(String providerName) { - synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider == null) { - return null; - } - return provider.getPropertiesLocked(); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager == null) { + return null; } + return manager.getProperties(); } @Override public boolean isProviderPackage(String packageName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, Manifest.permission.READ_DEVICE_CONFIG + " permission required"); - synchronized (mLock) { - for (LocationProviderManager provider : mProviders) { - if (provider.getPackagesLocked().contains(packageName)) { - return true; - } + for (LocationProviderManager manager : mProviderManagers) { + if (manager.getPackages().contains(packageName)) { + return true; } - return false; } + return false; } @Override public List getProviderPackages(String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, Manifest.permission.READ_DEVICE_CONFIG + " permission required"); - synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - return provider == null ? Collections.emptyList() : provider.getPackagesLocked(); - } + LocationProviderManager manager = getLocationProviderManager(providerName); + return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages()); } @Override @@ -2753,8 +2665,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (FUSED_PROVIDER.equals(providerName)) return false; synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - return provider != null && provider.isUseableLocked(userId); + LocationProviderManager manager = getLocationProviderManager(providerName); + return manager != null && manager.isUseable(userId); } } @@ -2792,37 +2704,39 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void handleLocationChangedLocked(Location location, LocationProviderManager provider) { - if (!mProviders.contains(provider)) { + private void handleLocationChangedLocked(Location location, LocationProviderManager manager) { + if (!mProviderManagers.contains(manager)) { + Log.w(TAG, "received location from unknown provider: " + manager.getName()); return; } if (!location.isComplete()) { - Log.w(TAG, "Dropping incomplete location: " + location); + Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: " + + location); return; } - // only notify passive provider and update last location for locations that come from - // useable providers - if (provider.isUseableLocked()) { - if (!provider.isPassiveLocked()) { - mPassiveProvider.updateLocation(location); - } + // notify passive provider + if (manager != mPassiveManager) { + mPassiveManager.updateLocation(new Location(location)); } if (D) Log.d(TAG, "incoming location: " + location); long now = SystemClock.elapsedRealtime(); - if (provider.isUseableLocked()) { - updateLastLocationLocked(location, provider.getName()); + + + // only update last location for locations that come from useable providers + if (manager.isUseable()) { + updateLastLocationLocked(location, manager.getName()); } // Update last known coarse interval location if enough time has passed. Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get( - provider.getName()); + manager.getName()); if (lastLocationCoarseInterval == null) { lastLocationCoarseInterval = new Location(location); - if (provider.isUseableLocked()) { - mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); + if (manager.isUseable()) { + mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval); } } long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos() @@ -2837,7 +2751,7 @@ public class LocationManagerService extends ILocationManager.Stub { lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); // Skip if there are no UpdateRecords for this provider. - ArrayList records = mRecordsByProvider.get(provider.getName()); + ArrayList records = mRecordsByProvider.get(manager.getName()); if (records == null || records.size() == 0) return; // Fetch coarse location @@ -2854,7 +2768,7 @@ public class LocationManagerService extends ILocationManager.Stub { Receiver receiver = r.mReceiver; boolean receiverDead = false; - if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) { + if (!manager.isUseable() && !isSettingsExemptLocked(r)) { continue; } @@ -2949,7 +2863,7 @@ public class LocationManagerService extends ILocationManager.Stub { for (UpdateRecord r : deadUpdateRecords) { r.disposeLocked(true); } - applyRequirementsLocked(provider); + applyRequirementsLocked(manager); } } @@ -3006,143 +2920,99 @@ public class LocationManagerService extends ILocationManager.Stub { // Mock Providers - private boolean canCallerAccessMockLocation(String opPackageName) { - return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), - opPackageName) == AppOpsManager.MODE_ALLOWED; - } - @Override - public void addTestProvider(String name, ProviderProperties properties, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void addTestProvider(String provider, ProviderProperties properties, + String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - if (PASSIVE_PROVIDER.equals(name)) { - throw new IllegalArgumentException("Cannot mock the passive location provider"); + synchronized (mLock) { + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + manager = new LocationProviderManager(provider); + mProviderManagers.add(manager); + } + + manager.setMockProvider(new MockProvider(mContext, properties)); + } + } + + @Override + public void removeTestProvider(String provider, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { + return; } synchronized (mLock) { - long identity = Binder.clearCallingIdentity(); - try { - LocationProviderManager oldProvider = getLocationProviderLocked(name); - if (oldProvider != null) { - removeProviderLocked(oldProvider); - } + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + return; + } - MockLocationProvider mockProviderManager = new MockLocationProvider(name); - addProviderLocked(mockProviderManager); - mockProviderManager.attachLocked( - new MockProvider(mContext, mockProviderManager, properties)); - } finally { - Binder.restoreCallingIdentity(identity); + manager.setMockProvider(null); + if (!manager.hasProvider()) { + mProviderManagers.remove(manager); + mLastLocation.remove(manager.getName()); + mLastLocationCoarseInterval.remove(manager.getName()); } } } @Override - public void removeTestProvider(String name, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void setTestProviderLocation(String provider, Location location, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - synchronized (mLock) { - long identity = Binder.clearCallingIdentity(); - try { - LocationProviderManager testProvider = getLocationProviderLocked(name); - if (testProvider == null || !testProvider.isMock()) { - return; - } - - removeProviderLocked(testProvider); - - // reinstate real provider if available - LocationProviderManager realProvider = null; - for (LocationProviderManager provider : mRealProviders) { - if (name.equals(provider.getName())) { - realProvider = provider; - break; - } - } - - if (realProvider != null) { - addProviderLocked(realProvider); - } - } finally { - Binder.restoreCallingIdentity(identity); - } + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + manager.setMockProviderLocation(location); } @Override - public void setTestProviderLocation(String providerName, Location location, - String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void setTestProviderEnabled(String provider, boolean enabled, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - String locationProvider = location.getProvider(); - if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) { - // The location has an explicit provider that is different from the mock - // provider name. The caller may be trying to fool us via b/33091107. - EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), - providerName + "!=" + location.getProvider()); - } - - ((MockLocationProvider) testProvider).setLocationLocked(location); - } - } - - @Override - public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { - return; + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - ((MockLocationProvider) testProvider).setEnabledLocked(enabled); - } + manager.setMockProviderEnabled(enabled); } @Override @NonNull - public List getTestProviderCurrentRequests(String providerName, - String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public List getTestProviderCurrentRequests(String provider, + String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return Collections.emptyList(); } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - MockLocationProvider provider = (MockLocationProvider) testProvider; - if (provider.mCurrentRequest == null) { - return Collections.emptyList(); - } - List requests = new ArrayList<>(); - for (LocationRequest request : provider.mCurrentRequest.locationRequests) { - requests.add(new LocationRequest(request)); - } - return requests; + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + return manager.getMockProviderRequests(); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); @@ -3224,25 +3094,27 @@ public class LocationManagerService extends ILocationManager.Stub { mLocationFudger.dump(fd, ipw, args); ipw.decreaseIndent(); } - - ipw.println("Location Settings:"); - ipw.increaseIndent(); - mSettingsStore.dump(fd, ipw, args); - ipw.decreaseIndent(); - - ipw.println("Location Providers:"); - ipw.increaseIndent(); - for (LocationProviderManager provider : mProviders) { - provider.dumpLocked(fd, ipw, args); - } - ipw.decreaseIndent(); } - if (mGnssManagerService != null) { - ipw.println("GNSS:"); - ipw.increaseIndent(); - mGnssManagerService.dump(fd, ipw, args); - ipw.decreaseIndent(); + ipw.println("Location Settings:"); + ipw.increaseIndent(); + mSettingsStore.dump(fd, ipw, args); + ipw.decreaseIndent(); + + ipw.println("Location Providers:"); + ipw.increaseIndent(); + for (LocationProviderManager manager : mProviderManagers) { + manager.dump(fd, ipw, args); + } + ipw.decreaseIndent(); + + synchronized (mLock) { + if (mGnssManagerService != null) { + ipw.println("GNSS:"); + ipw.increaseIndent(); + mGnssManagerService.dump(fd, ipw, args); + ipw.decreaseIndent(); + } } } } diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java index ccfc98e2291b6..ed6a759409d45 100644 --- a/services/core/java/com/android/server/location/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -16,11 +16,11 @@ package com.android.server.location; +import android.annotation.Nullable; import android.content.Context; import android.location.Location; import android.os.Binder; import android.os.Bundle; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -29,127 +29,336 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.UnaryOperator; /** - * Location Manager's interface for location providers. Always starts as disabled. + * Base class for all location providers. * * @hide */ public abstract class AbstractLocationProvider { /** - * Interface for communicating from a location provider back to the location service. + * Interface for listening to location providers. */ - public interface LocationProviderManager { + public interface Listener { /** - * May be called to inform the location service of a change in this location provider's - * enabled/disabled state. + * Called when a provider's state changes. May be invoked from any thread. Will be + * invoked with a cleared binder identity. */ - void onSetEnabled(boolean enabled); + void onStateChanged(State oldState, State newState); /** - * May be called to inform the location service of a change in this location provider's - * properties. - */ - void onSetProperties(ProviderProperties properties); - - /** - * May be called to inform the location service that this provider has a new location - * available. + * Called when a provider has a new location available. May be invoked from any thread. Will + * be invoked with a cleared binder identity. */ void onReportLocation(Location location); /** - * May be called to inform the location service that this provider has a new location - * available. + * Called when a provider has a new location available. May be invoked from any thread. Will + * be invoked with a cleared binder identity. */ void onReportLocation(List locations); } - protected final Context mContext; - private final LocationProviderManager mLocationProviderManager; + /** + * Holds a representation of the public state of a provider. + */ + public static final class State { - protected AbstractLocationProvider( - Context context, LocationProviderManager locationProviderManager) { + /** + * Default state value for a location provider that is disabled with no properties and an + * empty provider package list. + */ + public static final State EMPTY_STATE = new State(false, null, + Collections.emptySet()); + + /** + * The provider's enabled state. + */ + public final boolean enabled; + + /** + * The provider's properties. + */ + @Nullable public final ProviderProperties properties; + + /** + * The provider's package name list - provider packages may be afforded special privileges. + */ + public final Set providerPackageNames; + + private State(boolean enabled, ProviderProperties properties, + Set providerPackageNames) { + this.enabled = enabled; + this.properties = properties; + this.providerPackageNames = Objects.requireNonNull(providerPackageNames); + } + + private State withEnabled(boolean enabled) { + if (enabled == this.enabled) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + private State withProperties(ProviderProperties properties) { + if (properties.equals(this.properties)) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + private State withProviderPackageNames(Set providerPackageNames) { + if (providerPackageNames.equals(this.providerPackageNames)) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof State)) { + return false; + } + State state = (State) o; + return enabled == state.enabled && properties == state.properties + && providerPackageNames.equals(state.providerPackageNames); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, properties, providerPackageNames); + } + } + + // combines listener and state information so that they can be updated atomically with respect + // to each other and an ordering established. + private static class InternalState { + @Nullable public final Listener listener; + public final State state; + + private InternalState(@Nullable Listener listener, State state) { + this.listener = listener; + this.state = state; + } + + private InternalState withListener(Listener listener) { + if (listener == this.listener) { + return this; + } else { + return new InternalState(listener, state); + } + } + + private InternalState withState(State state) { + if (state.equals(this.state)) { + return this; + } else { + return new InternalState(listener, state); + } + } + + private InternalState withState(UnaryOperator operator) { + return withState(operator.apply(state)); + } + } + + protected final Context mContext; + protected final Executor mExecutor; + + // we use a lock-free implementation to update state to ensure atomicity between updating the + // provider state and setting the listener, so that the state updates a listener sees are + // consistent with when the listener was set (a listener should not see any updates that occur + // before it was set, and should not miss any updates that occur after it was set). + private final AtomicReference mInternalState; + + protected AbstractLocationProvider(Context context, Executor executor) { + this(context, executor, Collections.singleton(context.getPackageName())); + } + + protected AbstractLocationProvider(Context context, Executor executor, + Set packageNames) { mContext = context; - mLocationProviderManager = locationProviderManager; + mExecutor = executor; + mInternalState = new AtomicReference<>( + new InternalState(null, State.EMPTY_STATE.withProviderPackageNames(packageNames))); } /** - * Call this method to report a change in provider enabled/disabled status. May be called from - * any thread. + * Sets the listener and returns the state at the moment the listener was set. The listener can + * expect to receive all state updates from after this point. + */ + State setListener(@Nullable Listener listener) { + return mInternalState.updateAndGet( + internalState -> internalState.withListener(listener)).state; + } + + /** + * Retrieves the state of the provider. + */ + State getState() { + return mInternalState.get().state; + } + + /** + * Sets the state of the provider to the new state. + */ + void setState(State newState) { + InternalState oldInternalState = mInternalState.getAndUpdate( + internalState -> internalState.withState(newState)); + if (newState.equals(oldInternalState.state)) { + return; + } + + // we know that we only updated the state, so the listener for the old state is the same as + // the listener for the new state. + if (oldInternalState.listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + oldInternalState.listener.onStateChanged(oldInternalState.state, newState); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void setState(UnaryOperator operator) { + InternalState oldInternalState = mInternalState.getAndUpdate( + internalState -> internalState.withState(operator)); + + // recreate the new state from our knowledge of the old state - unfortunately may result in + // an extra allocation, but oh well... + State newState = operator.apply(oldInternalState.state); + + if (newState.equals(oldInternalState.state)) { + return; + } + + // we know that we only updated the state, so the listener for the old state is the same as + // the listener for the new state. + if (oldInternalState.listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + oldInternalState.listener.onStateChanged(oldInternalState.state, newState); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + /** + * The current enabled state of this provider. + */ + protected boolean isEnabled() { + return mInternalState.get().state.enabled; + } + + /** + * The current provider properties of this provider. + */ + @Nullable + protected ProviderProperties getProperties() { + return mInternalState.get().state.properties; + } + + /** + * The current package set of this provider. + */ + protected Set getProviderPackages() { + return mInternalState.get().state.providerPackageNames; + } + + /** + * Call this method to report a change in provider enabled/disabled status. */ protected void setEnabled(boolean enabled) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onSetEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); - } + setState(state -> state.withEnabled(enabled)); } /** - * Call this method to report a change in provider properties. May be called from - * any thread. + * Call this method to report a change in provider properties. */ protected void setProperties(ProviderProperties properties) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onSetProperties(properties); - } finally { - Binder.restoreCallingIdentity(identity); - } + setState(state -> state.withProperties(properties)); } /** - * Call this method to report a new location. May be called from any thread. + * Call this method to report a change in provider packages. + */ + protected void setPackageNames(Set packageNames) { + setState(state -> state.withProviderPackageNames(packageNames)); + } + + /** + * Call this method to report a new location. */ protected void reportLocation(Location location) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onReportLocation(location); - } finally { - Binder.restoreCallingIdentity(identity); + Listener listener = mInternalState.get().listener; + if (listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + listener.onReportLocation(location); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Call this method to report a new location. May be called from any thread. + * Call this method to report a new location. */ protected void reportLocation(List locations) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onReportLocation(locations); - } finally { - Binder.restoreCallingIdentity(identity); + Listener listener = mInternalState.get().listener; + if (listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + listener.onReportLocation(locations); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Invoked by the location service to return a list of packages currently associated with this - * provider. May be called from any thread. + * Sets a new request and worksource for the provider. */ - public List getProviderPackages() { - return Collections.singletonList(mContext.getPackageName()); + public final void setRequest(ProviderRequest request) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onSetRequest(request)); } /** - * Invoked by the location service to deliver a new request for fulfillment to the provider. - * Replaces any previous requests completely. Will always be invoked from the location service - * thread with a cleared binder identity. + * Always invoked on the provider executor. */ - public abstract void onSetRequest(ProviderRequest request, WorkSource source); + protected abstract void onSetRequest(ProviderRequest request); /** - * Invoked by the location service to deliver a custom command to this provider. Will always be - * invoked from the location service thread with a cleared binder identity. + * Sends an extra command to the provider for it to interpret as it likes. */ - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {} + public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras)); + } /** - * Invoked by the location service to dump debug or log information. May be invoked from any - * thread. + * Always invoked on the provider executor. + */ + protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} + + /** + * Dumps debug or log information. May be invoked from any thread. */ public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args); } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index d8561b697caa7..15cf190952d1d 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -43,6 +43,7 @@ import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; @@ -113,8 +114,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private static final ProviderProperties PROPERTIES = new ProviderProperties( - true, true, false, false, true, true, true, - Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); + /* requiresNetwork = */false, + /* requiresSatellite = */true, + /* requiresCell = */false, + /* hasMonetaryCost = */false, + /* supportAltitude = */true, + /* supportsSpeed = */true, + /* supportsBearing = */true, + Criteria.POWER_HIGH, + Criteria.ACCURACY_FINE); // these need to match GnssPositionMode enum in IGnss.hal private static final int GPS_POSITION_MODE_STANDALONE = 0; @@ -616,13 +624,12 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } - public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager, - Looper looper) { - super(context, locationProviderManager); + public GnssLocationProvider(Context context, Handler handler) { + super(context, new HandlerExecutor(handler)); ensureInitialized(); - mLooper = looper; + mLooper = handler.getLooper(); // Create a wake lock mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -639,7 +646,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context, - GnssLocationProvider.this::onNetworkAvailable, looper); + GnssLocationProvider.this::onNetworkAvailable, mLooper); // App ops service to keep track of who is accessing the GPS mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -649,7 +656,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements BatteryStats.SERVICE_NAME)); // Construct internal handler - mHandler = new ProviderHandler(looper); + mHandler = new ProviderHandler(mLooper); // Load GPS configuration and register listeners in the background: // some operations, such as opening files and registering broadcast receivers, can take a @@ -693,10 +700,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements }; mGnssMetrics = new GnssMetrics(mBatteryStats); - mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this); + mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this); GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper = new GnssSatelliteBlacklistHelper(mContext, - looper, this); + mLooper, this); mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist); mGnssBatchingProvider = new GnssBatchingProvider(); mGnssGeofenceProvider = new GnssGeofenceProvider(); @@ -1047,8 +1054,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { - sendMessage(SET_REQUEST, 0, new GpsRequest(request, source)); + public void onSetRequest(ProviderRequest request) { + sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource)); } private void handleSetRequest(ProviderRequest request, WorkSource source) { @@ -1185,7 +1192,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) { + public void onExtraCommand(int uid, int pid, String command, Bundle extras) { long identity = Binder.clearCallingIdentity(); try { @@ -2064,10 +2071,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } /** - * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager, - * Looper)}. - * It is in charge of loading properties and registering for events that will be posted to - * this handler. + * This method is bound to the constructor. It is in charge of loading properties and + * registering for events that will be posted to this handler. */ private void handleInitialize() { // class_init_native() already initializes the GNSS service handle during class loading. diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index 694f149046682..8a149afa62383 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -23,12 +23,13 @@ import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; -import android.os.WorkSource; +import android.util.ArraySet; import android.util.Log; -import com.android.internal.annotations.GuardedBy; import com.android.internal.location.ILocationProvider; import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderProperties; @@ -39,10 +40,8 @@ import com.android.server.ServiceWatcher; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; /** * Proxy for ILocationProvider implementations. @@ -52,59 +51,64 @@ public class LocationProviderProxy extends AbstractLocationProvider { private static final String TAG = "LocationProviderProxy"; private static final boolean D = LocationManagerService.D; - // used to ensure that updates to mProviderPackages are atomic - private final Object mProviderPackagesLock = new Object(); - - // used to ensure that updates to mRequest and mWorkSource are atomic - private final Object mRequestLock = new Object(); + private static final int MAX_ADDITIONAL_PACKAGES = 2; private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() { // executed on binder thread @Override public void onSetAdditionalProviderPackages(List packageNames) { - LocationProviderProxy.this.onSetAdditionalProviderPackages(packageNames); + int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()) + 1; + ArraySet allPackages = new ArraySet<>(maxCount); + allPackages.add(mServiceWatcher.getCurrentPackageName()); + for (String packageName : packageNames) { + if (packageNames.size() >= maxCount) { + return; + } + + try { + mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY); + allPackages.add(packageName); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: " + + packageName); + } + } + + setPackageNames(allPackages); } // executed on binder thread @Override public void onSetEnabled(boolean enabled) { - LocationProviderProxy.this.setEnabled(enabled); + setEnabled(enabled); } // executed on binder thread @Override public void onSetProperties(ProviderProperties properties) { - LocationProviderProxy.this.setProperties(properties); + setProperties(properties); } // executed on binder thread @Override public void onReportLocation(Location location) { - LocationProviderProxy.this.reportLocation(location); + reportLocation(location); } }; private final ServiceWatcher mServiceWatcher; - @GuardedBy("mProviderPackagesLock") - private final CopyOnWriteArrayList mProviderPackages = new CopyOnWriteArrayList<>(); - - @GuardedBy("mRequestLock") - @Nullable - private ProviderRequest mRequest; - @GuardedBy("mRequestLock") - private WorkSource mWorkSource; + @Nullable private ProviderRequest mRequest; /** * Creates a new LocationProviderProxy and immediately begins binding to the best applicable * service. */ @Nullable - public static LocationProviderProxy createAndBind( - Context context, LocationProviderManager locationProviderManager, String action, + public static LocationProviderProxy createAndBind(Context context, String action, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager, + LocationProviderProxy proxy = new LocationProviderProxy(context, FgThread.getHandler(), action, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId); if (proxy.bind()) { @@ -114,14 +118,13 @@ public class LocationProviderProxy extends AbstractLocationProvider { } } - private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager, - String action, int overlaySwitchResId, int defaultServicePackageNameResId, + private LocationProviderProxy(Context context, Handler handler, String action, + int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - super(context, locationProviderManager); + super(context, new HandlerExecutor(handler), Collections.emptySet()); mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, - FgThread.getHandler()) { + defaultServicePackageNameResId, initialPackageNamesResId, handler) { @Override protected void onBind() { @@ -130,14 +133,11 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Override protected void onUnbind() { - resetProviderPackages(Collections.emptyList()); - setEnabled(false); - setProperties(null); + setState(State.EMPTY_STATE); } }; mRequest = null; - mWorkSource = new WorkSource(); } private boolean bind() { @@ -148,77 +148,34 @@ public class LocationProviderProxy extends AbstractLocationProvider { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher); - resetProviderPackages(Collections.emptyList()); + setPackageNames(Collections.singleton(mServiceWatcher.getCurrentPackageName())); service.setLocationProviderManager(mManager); - synchronized (mRequestLock) { - if (mRequest != null) { - service.setRequest(mRequest, mWorkSource); - } + if (mRequest != null) { + service.setRequest(mRequest, mRequest.workSource); } } @Override - public List getProviderPackages() { - synchronized (mProviderPackagesLock) { - return mProviderPackages; - } - } - - @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { - synchronized (mRequestLock) { - mRequest = request; - mWorkSource = source; - } + public void onSetRequest(ProviderRequest request) { mServiceWatcher.runOnBinder(binder -> { + mRequest = request; ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - service.setRequest(request, source); + service.setRequest(request, request.workSource); }); } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("service=" + mServiceWatcher); - synchronized (mProviderPackagesLock) { - if (mProviderPackages.size() > 1) { - pw.println("additional packages=" + mProviderPackages); - } - } - } - - @Override - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) { + public void onExtraCommand(int uid, int pid, String command, Bundle extras) { mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); service.sendExtraCommand(command, extras); }); } - private void onSetAdditionalProviderPackages(List packageNames) { - resetProviderPackages(packageNames); - } - - private void resetProviderPackages(List additionalPackageNames) { - ArrayList permittedPackages = new ArrayList<>(additionalPackageNames.size()); - for (String packageName : additionalPackageNames) { - try { - mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY); - permittedPackages.add(packageName); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: " - + packageName); - } - } - - synchronized (mProviderPackagesLock) { - mProviderPackages.clear(); - String myPackage = mServiceWatcher.getCurrentPackageName(); - if (myPackage != null) { - mProviderPackages.add(myPackage); - mProviderPackages.addAll(permittedPackages); - } - } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("service=" + mServiceWatcher); } } diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java index f625452975c0b..0e8720ebb08ff 100644 --- a/services/core/java/com/android/server/location/LocationSettingsStore.java +++ b/services/core/java/com/android/server/location/LocationSettingsStore.java @@ -16,6 +16,8 @@ package com.android.server.location; +import static android.location.LocationManager.FUSED_PROVIDER; +import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS; @@ -28,6 +30,7 @@ import android.app.ActivityManager; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; +import android.os.Binder; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -248,6 +251,9 @@ public class LocationSettingsStore { DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS); } + /** + * Retrieve maximum age of the last location. + */ public long getMaxLastLocationAgeMs() { return Settings.Global.getLong( mContext.getContentResolver(), @@ -255,6 +261,29 @@ public class LocationSettingsStore { DEFAULT_MAX_LAST_LOCATION_AGE_MS); } + /** + * Set a value for the deprecated LOCATION_PROVIDERS_ALLOWED setting. This is used purely for + * backwards compatibility for old clients, and may be removed in the future. + */ + public void setLocationProviderAllowed(String provider, boolean enabled, int userId) { + // fused and passive provider never get public updates for legacy reasons + if (FUSED_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) { + return; + } + + long identity = Binder.clearCallingIdentity(); + try { + // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + (enabled ? "+" : "-") + provider, + userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + /** * Dump info for debugging. */ diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java index 472876bfd86a9..60c9fc12c2010 100644 --- a/services/core/java/com/android/server/location/MockProvider.java +++ b/services/core/java/com/android/server/location/MockProvider.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.annotation.Nullable; import android.content.Context; import android.location.Location; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -34,41 +33,33 @@ import java.io.PrintWriter; */ public class MockProvider extends AbstractLocationProvider { - private boolean mEnabled; @Nullable private Location mLocation; - public MockProvider(Context context, - LocationProviderManager locationProviderManager, ProviderProperties properties) { - super(context, locationProviderManager); - - mEnabled = true; - mLocation = null; - + public MockProvider(Context context, ProviderProperties properties) { + // using a direct executor is only acceptable because this class is so simple it is trivial + // to verify that it does not acquire any locks or re-enter LMS from callbacks + super(context, Runnable::run); setProperties(properties); } /** Sets the enabled state of this mock provider. */ - public void setEnabled(boolean enabled) { - mEnabled = enabled; - super.setEnabled(enabled); + public void setProviderEnabled(boolean enabled) { + setEnabled(enabled); } /** Sets the location to report for this mock provider. */ - public void setLocation(Location l) { - mLocation = new Location(l); - if (!mLocation.isFromMockProvider()) { - mLocation.setIsFromMockProvider(true); - } - if (mEnabled) { - reportLocation(mLocation); - } + public void setProviderLocation(Location l) { + Location location = new Location(l); + location.setIsFromMockProvider(true); + mLocation = location; + reportLocation(location); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("last location=" + mLocation); + pw.println("last mock location=" + mLocation); } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) {} + public void onSetRequest(ProviderRequest request) {} } diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java new file mode 100644 index 0000000000000..f50dfe7edbb7e --- /dev/null +++ b/services/core/java/com/android/server/location/MockableLocationProvider.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2019 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; + +import android.annotation.Nullable; +import android.content.Context; +import android.location.Location; +import android.os.Bundle; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.location.ProviderRequest; +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; + +/** + * Represents a location provider that may switch between a mock implementation and a real + * implementation. Requires owners to provide a lock object that will be used internally and held + * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead + * to deadlock. + * + * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can + * be held BOTH when calling into this class AND when receiving a callback from this class is the + * lock given to this class via the constructor. Holding any other lock is ok as long as there is no + * possibility that it can be obtained within both codepaths. + * + * Holding the given lock guarantees atomicity of any operations on this class for the duration. + * + * @hide + */ +public class MockableLocationProvider extends AbstractLocationProvider { + + private final Object mOwnerLock; + + @GuardedBy("mOwnerLock") + @Nullable private AbstractLocationProvider mProvider; + @GuardedBy("mOwnerLock") + @Nullable private AbstractLocationProvider mRealProvider; + @GuardedBy("mOwnerLock") + @Nullable private MockProvider mMockProvider; + + @GuardedBy("mOwnerLock") + private ProviderRequest mRequest; + + /** + * The given lock object will be held any time the listener is invoked, and may also be acquired + * and released during the course of invoking any public methods. Holding the given lock ensures + * that provider state cannot change except as result of an explicit call by the owner of the + * lock into this class. The client is reponsible for ensuring this cannot cause deadlock. + * + * The client should expect that it may being to receive callbacks as soon as this constructor + * is invoked. + */ + public MockableLocationProvider(Context context, Object ownerLock, Listener listener) { + // using a direct executor is acceptable because all inbound calls are delegated to the + // actual provider implementations which will use their own executors + super(context, Runnable::run, Collections.emptySet()); + mOwnerLock = ownerLock; + mRequest = ProviderRequest.EMPTY_REQUEST; + + setListener(listener); + } + + /** + * Returns the current provider implementation. May be null if there is no current + * implementation. + */ + @Nullable + public AbstractLocationProvider getProvider() { + synchronized (mOwnerLock) { + return mProvider; + } + } + + /** + * Sets the real provider implementation, replacing any previous real provider implementation. + * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this + * results in a state change. + */ + public void setRealProvider(@Nullable AbstractLocationProvider provider) { + synchronized (mOwnerLock) { + if (mRealProvider == provider) { + return; + } + + mRealProvider = provider; + if (!isMock()) { + setProviderLocked(mRealProvider); + } + } + } + + /** + * Sets the mock provider implementation, replacing any previous mock provider implementation. + * Mock implementations are always used instead of real implementations if set. May cause an + * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a + * state change. + */ + public void setMockProvider(@Nullable MockProvider provider) { + synchronized (mOwnerLock) { + if (mMockProvider == provider) { + return; + } + + mMockProvider = provider; + if (mMockProvider != null) { + setProviderLocked(mMockProvider); + } else { + setProviderLocked(mRealProvider); + } + } + } + + @GuardedBy("mOwnerLock") + private void setProviderLocked(@Nullable AbstractLocationProvider provider) { + if (mProvider == provider) { + return; + } + + AbstractLocationProvider oldProvider = mProvider; + mProvider = provider; + + if (oldProvider != null) { + // do this after switching the provider - so even if the old provider is using a direct + // executor, if it re-enters this class within setRequest(), it will be ignored + oldProvider.setListener(null); + oldProvider.setRequest(ProviderRequest.EMPTY_REQUEST); + } + + State newState; + if (mProvider != null) { + newState = mProvider.setListener(new ListenerWrapper(mProvider)); + } else { + newState = State.EMPTY_STATE; + } + + ProviderRequest oldRequest = mRequest; + setState(newState); + + if (mProvider != null && oldRequest == mRequest) { + mProvider.setRequest(mRequest); + } + } + + /** + * Returns true if the current active provider implementation is the mock implementation, and + * false otherwise. + */ + public boolean isMock() { + synchronized (mOwnerLock) { + return mMockProvider != null && mProvider == mMockProvider; + } + } + + /** + * Sets the mock provider implementation's enabled state. Will throw an exception if the mock + * provider is not currently the active implementation. + */ + public void setMockProviderEnabled(boolean enabled) { + synchronized (mOwnerLock) { + Preconditions.checkState(isMock()); + mMockProvider.setProviderEnabled(enabled); + } + } + /** + * Sets the mock provider implementation's location. Will throw an exception if the mock + * provider is not currently the active implementation. + */ + public void setMockProviderLocation(Location location) { + synchronized (mOwnerLock) { + Preconditions.checkState(isMock()); + mMockProvider.setProviderLocation(location); + } + } + + @Override + public State getState() { + return super.getState(); + } + + /** + * Returns the current location request. + */ + public ProviderRequest getCurrentRequest() { + synchronized (mOwnerLock) { + return mRequest; + } + } + + protected void onSetRequest(ProviderRequest request) { + synchronized (mOwnerLock) { + if (request == mRequest) { + return; + } + + mRequest = request; + + if (mProvider != null) { + mProvider.setRequest(request); + } + } + } + + protected void onExtraCommand(int uid, int pid, String command, Bundle extras) { + synchronized (mOwnerLock) { + if (mProvider != null) { + mProvider.sendExtraCommand(uid, pid, command, extras); + } + } + } + + /** + * Dumps the current provider implementation. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + AbstractLocationProvider provider; + synchronized (mOwnerLock) { + provider = mProvider; + pw.println("request=" + mRequest); + } + + if (provider != null) { + // dump outside the lock in case the provider wants to acquire its own locks, and since + // the default provider dump behavior does not move things onto the provider thread... + provider.dump(fd, pw, args); + } + } + + // ensures that callbacks from the incorrect provider are never visible to clients - this + // requires holding the owner's lock for the duration of the callback + private class ListenerWrapper implements Listener { + + private final AbstractLocationProvider mListenerProvider; + + private ListenerWrapper(AbstractLocationProvider listenerProvider) { + mListenerProvider = listenerProvider; + } + + @Override + public final void onStateChanged(State oldState, State newState) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + setState(newState); + } + } + + @Override + public final void onReportLocation(Location location) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + reportLocation(location); + } + } + + @Override + public final void onReportLocation(List locations) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + reportLocation(locations); + } + } + } +} diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index 639b1eb1ed5e0..b33877069d70c 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.content.Context; import android.location.Criteria; import android.location.Location; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -37,13 +36,22 @@ import java.io.PrintWriter; public class PassiveProvider extends AbstractLocationProvider { private static final ProviderProperties PROPERTIES = new ProviderProperties( - false, false, false, false, false, false, false, - Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); + /* requiresNetwork = */false, + /* requiresSatellite = */false, + /* requiresCell = */false, + /* hasMonetaryCost = */false, + /* supportsAltitude = */false, + /* supportsSpeed = */false, + /* supportsBearing = */false, + Criteria.POWER_LOW, + Criteria.ACCURACY_COARSE); - private boolean mReportLocation; + private volatile boolean mReportLocation; - public PassiveProvider(Context context, LocationProviderManager locationProviderManager) { - super(context, locationProviderManager); + public PassiveProvider(Context context) { + // using a direct executor is only acceptable because this class is so simple it is trivial + // to verify that it does not acquire any locks or re-enter LMS from callbacks + super(context, Runnable::run); mReportLocation = false; @@ -52,7 +60,7 @@ public class PassiveProvider extends AbstractLocationProvider { } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { + public void onSetRequest(ProviderRequest request) { mReportLocation = request.reportLocation; } @@ -63,7 +71,5 @@ public class PassiveProvider extends AbstractLocationProvider { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("report location=" + mReportLocation); - } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {} }