| /* |
| * 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); |
| } |
| } |
| } |