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) {} }