blob: 0e7e180bce65ea52e6d2b34f8488bd4acd1d33a9 [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.core.location;
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static java.lang.Math.min;
import android.location.LocationRequest;
import android.os.Build.VERSION;
import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.util.Preconditions;
import androidx.core.util.TimeUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Compatibility version of {@link LocationRequest}.
*/
public final class LocationRequestCompat {
/**
* Represents a passive only request. Such a request will not trigger any active locations or
* power usage itself, but may receive locations generated in response to other requests.
*
* @see LocationRequestCompat#getIntervalMillis()
*/
public static final long PASSIVE_INTERVAL = LocationRequest.PASSIVE_INTERVAL;
/** @hide */
@RestrictTo(LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({QUALITY_LOW_POWER, QUALITY_BALANCED_POWER_ACCURACY, QUALITY_HIGH_ACCURACY})
public @interface Quality {
}
/**
* A quality constant indicating a location provider may choose to satisfy this request by
* providing very accurate locations at the expense of potentially increased power usage. Each
* location provider may interpret this field differently, but as an example, the network
* provider may choose to return only wifi based locations rather than cell based locations in
* order to have greater accuracy when this flag is present.
*/
public static final int QUALITY_HIGH_ACCURACY = LocationRequest.QUALITY_HIGH_ACCURACY;
/**
* A quality constant indicating a location provider may choose to satisfy this request by
* equally balancing power and accuracy constraints. Each location provider may interpret this
* field differently, but location providers will generally use their default behavior when this
* flag is present.
*/
public static final int QUALITY_BALANCED_POWER_ACCURACY =
LocationRequest.QUALITY_BALANCED_POWER_ACCURACY;
/**
* A quality constant indicating a location provider may choose to satisfy this request by
* providing less accurate locations in order to save power. Each location provider may
* interpret this field differently, but as an example, the network provider may choose to
* return cell based locations rather than wifi based locations in order to save power when this
* flag is present.
*/
public static final int QUALITY_LOW_POWER = LocationRequest.QUALITY_LOW_POWER;
private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1;
private static Method sCreateFromDeprecatedProviderMethod;
private static Method sSetQualityMethod;
private static Method sSetFastestIntervalMethod;
private static Method sSetNumUpdatesMethod;
private static Method sSetExpireInMethod;
@Quality
final int mQuality;
final long mIntervalMillis;
final long mMinUpdateIntervalMillis;
final long mDurationMillis;
final int mMaxUpdates;
final float mMinUpdateDistanceMeters;
final long mMaxUpdateDelayMillis;
LocationRequestCompat(
long intervalMillis,
@Quality int quality,
long durationMillis,
int maxUpdates,
long minUpdateIntervalMillis,
float minUpdateDistanceMeters,
long maxUpdateDelayMillis) {
mIntervalMillis = intervalMillis;
mQuality = quality;
mMinUpdateIntervalMillis = minUpdateIntervalMillis;
mDurationMillis = durationMillis;
mMaxUpdates = maxUpdates;
mMinUpdateDistanceMeters = minUpdateDistanceMeters;
mMaxUpdateDelayMillis = maxUpdateDelayMillis;
}
/**
* Returns the quality hint for this location request. The quality hint informs the provider how
* it should attempt to manage any accuracy vs power tradeoffs while attempting to satisfy this
* location request.
*/
public @Quality int getQuality() {
return mQuality;
}
/**
* Returns the desired interval of location updates, or {@link #PASSIVE_INTERVAL} if this is a
* passive, no power request. A passive request will not actively generate location updates
* (and thus will not be power blamed for location), but may receive location updates generated
* as a result of other location requests. A passive request must always have an explicit
* minimum update interval set.
*
* <p>Locations may be available at a faster interval than specified here, see
* {@link #getMinUpdateIntervalMillis()} for the behavior in that case.
*/
public @IntRange(from = 0) long getIntervalMillis() {
return mIntervalMillis;
}
/**
* Returns the minimum update interval. If location updates are available faster than the
* request interval then locations will only be updated if the minimum update interval has
* expired since the last location update.
*
* <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the
* minimum update interval, so you need not worry about updates blocked simply because they
* arrived a fraction of a second earlier than expected.
*
* @return the minimum update interval
*/
public @IntRange(from = 0) long getMinUpdateIntervalMillis() {
if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) {
return mIntervalMillis;
} else {
return mMinUpdateIntervalMillis;
}
}
/**
* Returns the duration for which location will be provided before the request is automatically
* removed. A duration of <code>Long.MAX_VALUE</code> represents an unlimited duration.
*
* @return the duration for which location will be provided
*/
public @IntRange(from = 1) long getDurationMillis() {
return mDurationMillis;
}
/**
* Returns the maximum number of location updates for this request before the request is
* automatically removed. A max updates value of <code>Integer.MAX_VALUE</code> represents an
* unlimited number of updates.
*/
public @IntRange(from = 1, to = Integer.MAX_VALUE) int getMaxUpdates() {
return mMaxUpdates;
}
/**
* Returns the minimum distance between location updates. If a potential location update is
* closer to the last location update than the minimum update distance, then the potential
* location update will not occur. A value of 0 meters implies that no location update will ever
* be rejected due to failing this constraint.
*
* @return the minimum distance between location updates
*/
public @FloatRange(from = 0, to = Float.MAX_VALUE) float getMinUpdateDistanceMeters() {
return mMinUpdateDistanceMeters;
}
/**
* Returns the maximum time any location update may be delayed, and thus grouped with following
* updates to enable location batching. If the maximum update delay is equal to or greater than
* twice the interval, then location providers may provide batched results. The maximum batch
* size is the maximum update delay divided by the interval. Not all devices or location
* providers support batching, and use of this parameter does not guarantee that the client will
* see batched results, or that batched results will always be of the maximum size.
*
* When available, batching can provide substantial power savings to the device, and clients are
* encouraged to take advantage where appropriate for the use case.
*
* @return the maximum time by which a location update may be delayed
* @see LocationListenerCompat#onLocationChanged(java.util.List)
*/
public @IntRange(from = 0) long getMaxUpdateDelayMillis() {
return mMaxUpdateDelayMillis;
}
/**
* Converts an instance to an equivalent {@link LocationRequest}.
*
* @return platform class object
* @see LocationRequest
*/
@RequiresApi(31)
@NonNull
public LocationRequest toLocationRequest() {
return new LocationRequest.Builder(getIntervalMillis())
.setQuality(getQuality())
.setMinUpdateIntervalMillis(getMinUpdateIntervalMillis())
.setDurationMillis(getDurationMillis())
.setMaxUpdates(getMaxUpdates())
.setMinUpdateDistanceMeters(getMinUpdateDistanceMeters())
.setMaxUpdateDelayMillis(getMaxUpdateDelayMillis())
.build();
}
/**
* Converts an instance to an equivalent {@link LocationRequest}, with the provider field of
* the resulting LocationRequest set to the provider argument provided to this method.
*
* <p>May return null on some SDKs if various reflective operations fail. This should only
* occur on non-standard Android devices, and thus should be rare.
*
* @return platform class object
* @see LocationRequest
*/
@SuppressWarnings("JavaReflectionMemberAccess")
@RequiresApi(19)
@Nullable
public LocationRequest toLocationRequest(@NonNull String provider) {
if (VERSION.SDK_INT >= 31) {
return toLocationRequest();
} else {
try {
if (sCreateFromDeprecatedProviderMethod == null) {
sCreateFromDeprecatedProviderMethod = LocationRequest.class.getDeclaredMethod(
"createFromDeprecatedProvider", String.class, long.class, float.class,
boolean.class);
sCreateFromDeprecatedProviderMethod.setAccessible(true);
}
LocationRequest request =
(LocationRequest) sCreateFromDeprecatedProviderMethod.invoke(null, provider,
getIntervalMillis(),
getMinUpdateDistanceMeters(), false);
if (request == null) {
return null;
}
if (sSetQualityMethod == null) {
sSetQualityMethod = LocationRequest.class.getDeclaredMethod(
"setQuality", int.class);
sSetQualityMethod.setAccessible(true);
}
sSetQualityMethod.invoke(request, getQuality());
if (sSetFastestIntervalMethod == null) {
sSetFastestIntervalMethod = LocationRequest.class.getDeclaredMethod(
"setFastestInterval", long.class);
sSetFastestIntervalMethod.setAccessible(true);
}
sSetFastestIntervalMethod.invoke(request, getMinUpdateIntervalMillis());
if (getMaxUpdates() < Integer.MAX_VALUE) {
if (sSetNumUpdatesMethod == null) {
sSetNumUpdatesMethod = LocationRequest.class.getDeclaredMethod(
"setNumUpdates", int.class);
sSetNumUpdatesMethod.setAccessible(true);
}
sSetNumUpdatesMethod.invoke(request, getMaxUpdates());
}
if (getDurationMillis() < Long.MAX_VALUE) {
if (sSetExpireInMethod == null) {
sSetExpireInMethod = LocationRequest.class.getDeclaredMethod(
"setExpireIn", long.class);
sSetExpireInMethod.setAccessible(true);
}
sSetExpireInMethod.invoke(request, getDurationMillis());
}
return request;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
return null;
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof LocationRequestCompat)) {
return false;
}
LocationRequestCompat that = (LocationRequestCompat) o;
return mQuality == that.mQuality && mIntervalMillis == that.mIntervalMillis
&& mMinUpdateIntervalMillis == that.mMinUpdateIntervalMillis
&& mDurationMillis == that.mDurationMillis && mMaxUpdates == that.mMaxUpdates
&& Float.compare(that.mMinUpdateDistanceMeters, mMinUpdateDistanceMeters) == 0
&& mMaxUpdateDelayMillis == that.mMaxUpdateDelayMillis;
}
@Override
public int hashCode() {
int result = mQuality;
result = 31 * result + (int) (mIntervalMillis ^ (mIntervalMillis >>> 32));
result = 31 * result + (int) (mMinUpdateIntervalMillis ^ (mMinUpdateIntervalMillis >>> 32));
return result;
}
@Override
@NonNull
public String toString() {
StringBuilder s = new StringBuilder();
s.append("Request[");
if (mIntervalMillis != PASSIVE_INTERVAL) {
s.append("@");
TimeUtils.formatDuration(mIntervalMillis, s);
switch (mQuality) {
case QUALITY_HIGH_ACCURACY:
s.append(" HIGH_ACCURACY");
break;
case QUALITY_BALANCED_POWER_ACCURACY:
s.append(" BALANCED");
break;
case QUALITY_LOW_POWER:
s.append(" LOW_POWER");
break;
}
} else {
s.append("PASSIVE");
}
if (mDurationMillis != Long.MAX_VALUE) {
s.append(", duration=");
TimeUtils.formatDuration(mDurationMillis, s);
}
if (mMaxUpdates != Integer.MAX_VALUE) {
s.append(", maxUpdates=").append(mMaxUpdates);
}
if (mMinUpdateIntervalMillis != IMPLICIT_MIN_UPDATE_INTERVAL
&& mMinUpdateIntervalMillis < mIntervalMillis) {
s.append(", minUpdateInterval=");
TimeUtils.formatDuration(mMinUpdateIntervalMillis, s);
}
if (mMinUpdateDistanceMeters > 0.0) {
s.append(", minUpdateDistance=").append(mMinUpdateDistanceMeters);
}
if (mMaxUpdateDelayMillis / 2 > mIntervalMillis) {
s.append(", maxUpdateDelay=");
TimeUtils.formatDuration(mMaxUpdateDelayMillis, s);
}
s.append(']');
return s.toString();
}
/**
* A builder class for {@link LocationRequestCompat}.
*/
public static final class Builder {
private long mIntervalMillis;
private @Quality int mQuality;
private long mDurationMillis;
private int mMaxUpdates;
private long mMinUpdateIntervalMillis;
private float mMinUpdateDistanceMeters;
private long mMaxUpdateDelayMillis;
/**
* Creates a new Builder with the given interval. See {@link #setIntervalMillis(long)} for
* more information on the interval. Note that the defaults for various Builder parameters
* may be different from the defaults for the framework {@link LocationRequest}.
*/
public Builder(long intervalMillis) {
// gives us a range check
setIntervalMillis(intervalMillis);
mQuality = QUALITY_BALANCED_POWER_ACCURACY;
mDurationMillis = Long.MAX_VALUE;
mMaxUpdates = Integer.MAX_VALUE;
mMinUpdateIntervalMillis = IMPLICIT_MIN_UPDATE_INTERVAL;
mMinUpdateDistanceMeters = 0;
mMaxUpdateDelayMillis = 0;
}
/**
* Creates a new Builder with all parameters copied from the given location request.
*/
public Builder(@NonNull LocationRequestCompat locationRequest) {
mIntervalMillis = locationRequest.mIntervalMillis;
mQuality = locationRequest.mQuality;
mDurationMillis = locationRequest.mDurationMillis;
mMaxUpdates = locationRequest.mMaxUpdates;
mMinUpdateIntervalMillis = locationRequest.mMinUpdateIntervalMillis;
mMinUpdateDistanceMeters = locationRequest.mMinUpdateDistanceMeters;
mMaxUpdateDelayMillis = locationRequest.mMaxUpdateDelayMillis;
}
/**
* Sets the request interval. The request interval may be set to {@link #PASSIVE_INTERVAL}
* which indicates this request will not actively generate location updates (and thus will
* not be power blamed for location), but may receive location updates generated as a result
* of other location requests. A passive request must always have an explicit minimum
* update interval set.
*
* <p>Locations may be available at a faster interval than specified here, see
* {@link #setMinUpdateIntervalMillis(long)} for the behavior in that case.
*
* <p class="note"><strong>Note:</strong> On platforms below Android 12, using the
* {@link #PASSIVE_INTERVAL} will not result in a truly passive request, but a request with
* an extremely long interval. In most cases, this is effectively the same as a passive
* request, but this may occasionally result in an initial location calculation for which
* the client will be blamed.
*/
public @NonNull Builder setIntervalMillis(@IntRange(from = 0) long intervalMillis) {
mIntervalMillis = Preconditions.checkArgumentInRange(intervalMillis, 0, Long
.MAX_VALUE,
"intervalMillis");
return this;
}
/**
* Sets the request quality. The quality is a hint to providers on how they should weigh
* power vs accuracy tradeoffs. High accuracy locations may cost more power to produce, and
* lower accuracy locations may cost less power to produce. Defaults to
* {@link #QUALITY_BALANCED_POWER_ACCURACY}.
*/
public @NonNull Builder setQuality(@Quality int quality) {
Preconditions.checkArgument(
quality == QUALITY_LOW_POWER || quality == QUALITY_BALANCED_POWER_ACCURACY
|| quality == QUALITY_HIGH_ACCURACY,
"quality must be a defined QUALITY constant, not %d", quality);
mQuality = quality;
return this;
}
/**
* Sets the duration this request will continue before being automatically removed. Defaults
* to <code>Long.MAX_VALUE</code>, which represents an unlimited duration.
*
* <p class="note"><strong>Note:</strong> This parameter will be ignored on platforms below
* Android Kitkat, and the request will not be removed after the duration expires.
*/
public @NonNull Builder setDurationMillis(@IntRange(from = 1) long durationMillis) {
mDurationMillis = Preconditions.checkArgumentInRange(durationMillis, 1, Long
.MAX_VALUE,
"durationMillis");
return this;
}
/**
* Sets the maximum number of location updates for this request before this request is
* automatically removed. Defaults to <code>Integer.MAX_VALUE</code>, which represents an
* unlimited number of updates.
*/
public @NonNull Builder setMaxUpdates(
@IntRange(from = 1, to = Integer.MAX_VALUE) int maxUpdates) {
mMaxUpdates = Preconditions.checkArgumentInRange(maxUpdates, 1, Integer.MAX_VALUE,
"maxUpdates");
return this;
}
/**
* Sets an explicit minimum update interval. If location updates are available faster than
* the request interval then an update will only occur if the minimum update interval has
* expired since the last location update. Defaults to no explicit minimum update interval
* set, which means the minimum update interval is the same as the interval.
*
* <p class=note><strong>Note:</strong> Some allowance for jitter is already built into the
* minimum update interval, so you need not worry about updates blocked simply because they
* arrived a fraction of a second earlier than expected.
*
* <p class="note"><strong>Note:</strong> When {@link #build()} is invoked, the minimum of
* the interval and the minimum update interval will be used as the minimum update interval
* of the built request.
*/
public @NonNull Builder setMinUpdateIntervalMillis(
@IntRange(from = 0) long minUpdateIntervalMillis) {
mMinUpdateIntervalMillis = Preconditions.checkArgumentInRange(minUpdateIntervalMillis,
0, Long.MAX_VALUE, "minUpdateIntervalMillis");
return this;
}
/**
* Clears an explicitly set minimum update interval and reverts to an implicit minimum
* update interval (ie, the minimum update interval is the same value as the interval).
*/
public @NonNull Builder clearMinUpdateIntervalMillis() {
mMinUpdateIntervalMillis = IMPLICIT_MIN_UPDATE_INTERVAL;
return this;
}
/**
* Sets the minimum update distance between location updates. If a potential location
* update is closer to the last location update than the minimum update distance, then
* the potential location update will not occur. Defaults to 0, which represents no minimum
* update distance.
*/
public @NonNull Builder setMinUpdateDistanceMeters(
@FloatRange(from = 0, to = Float.MAX_VALUE) float minUpdateDistanceMeters) {
mMinUpdateDistanceMeters = minUpdateDistanceMeters;
mMinUpdateDistanceMeters = Preconditions.checkArgumentInRange(minUpdateDistanceMeters,
0, Float.MAX_VALUE, "minUpdateDistanceMeters");
return this;
}
/**
* Sets the maximum time any location update may be delayed, and thus grouped with following
* updates to enable location batching. If the maximum update delay is equal to or greater
* than twice the interval, then location providers may provide batched results. Defaults to
* 0, which represents no batching allowed.
*/
public @NonNull Builder setMaxUpdateDelayMillis(
@IntRange(from = 0) long maxUpdateDelayMillis) {
mMaxUpdateDelayMillis = maxUpdateDelayMillis;
mMaxUpdateDelayMillis = Preconditions.checkArgumentInRange(maxUpdateDelayMillis, 0,
Long.MAX_VALUE, "maxUpdateDelayMillis");
return this;
}
/**
* Builds a location request from this builder. If an explicit minimum update interval is
* set, the minimum update interval of the location request will be the minimum of the
* interval and minimum update interval.
*
* <p>If building a passive request then you must have set an explicit minimum update
* interval.
*/
public @NonNull LocationRequestCompat build() {
Preconditions.checkState(mIntervalMillis != PASSIVE_INTERVAL
|| mMinUpdateIntervalMillis != IMPLICIT_MIN_UPDATE_INTERVAL,
"passive location requests must have an explicit minimum update interval");
return new LocationRequestCompat(
mIntervalMillis,
mQuality,
mDurationMillis,
mMaxUpdates,
min(mMinUpdateIntervalMillis, mIntervalMillis),
mMinUpdateDistanceMeters,
mMaxUpdateDelayMillis);
}
}
}