/* * Copyright (C) 2012 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 android.location; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.os.WorkSource; import android.util.TimeUtils; /** * A data object that contains quality of service parameters for requests * to the {@link LocationManager}. * *

LocationRequest objects are used to request a quality of service * for location updates from the Location Manager. * *

For example, if your application wants high accuracy location * it should create a location request with {@link #setQuality} set to * {@link #ACCURACY_FINE} or {@link #POWER_HIGH}, and it should set * {@link #setInterval} to less than one second. This would be * appropriate for mapping applications that are showing your location * in real-time. * *

At the other extreme, if you want negligible power * impact, but to still receive location updates when available, then use * {@link #setQuality} with {@link #POWER_NONE}. With this request your * application will not trigger (and therefore will not receive any * power blame) any location updates, but will receive locations * triggered by other applications. This would be appropriate for * applications that have no firm requirement for location, but can * take advantage when available. * *

In between these two extremes is a very common use-case, where * applications definitely want to receive * updates at a specified interval, and can receive them faster when * available, but still want a low power impact. These applications * should consider {@link #POWER_LOW} combined with a faster * {@link #setFastestInterval} (such as 1 minute) and a slower * {@link #setInterval} (such as 60 minutes). They will only be assigned * power blame for the interval set by {@link #setInterval}, but can * still receive locations triggered by other applications at a rate up * to {@link #setFastestInterval}. This style of request is appropriate for * many location aware applications, including background usage. Do be * careful to also throttle {@link #setFastestInterval} if you perform * heavy-weight work after receiving an update - such as using the network. * *

Activities should strongly consider removing all location * request when entering the background * (for example at {@link android.app.Activity#onPause}), or * at least swap the request to a larger interval and lower quality. * Future version of the location manager may automatically perform background * throttling on behalf of applications. * *

Applications cannot specify the exact location sources that are * used by Android's Fusion Engine. In fact, the system * may have multiple location sources (providers) running and may * fuse the results from several sources into a single Location object. * *

Location requests from applications with * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and not * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} will * be automatically throttled to a slower interval, and the location * object will be obfuscated to only show a coarse level of accuracy. * *

All location requests are considered hints, and you may receive * locations that are more accurate, less accurate, and slower * than requested. * * @hide */ @SystemApi public final class LocationRequest implements Parcelable { /** * Used with {@link #setQuality} to request the most accurate locations available. * *

This may be up to 1 meter accuracy, although this is implementation dependent. */ public static final int ACCURACY_FINE = 100; /** * Used with {@link #setQuality} to request "block" level accuracy. * *

Block level accuracy is considered to be about 100 meter accuracy, * although this is implementation dependent. Using a coarse accuracy * such as this often consumes less power. */ public static final int ACCURACY_BLOCK = 102; /** * Used with {@link #setQuality} to request "city" level accuracy. * *

City level accuracy is considered to be about 10km accuracy, * although this is implementation dependent. Using a coarse accuracy * such as this often consumes less power. */ public static final int ACCURACY_CITY = 104; /** * Used with {@link #setQuality} to require no direct power impact (passive locations). * *

This location request will not trigger any active location requests, * but will receive locations triggered by other applications. Your application * will not receive any direct power blame for location work. */ public static final int POWER_NONE = 200; /** * Used with {@link #setQuality} to request low power impact. * *

This location request will avoid high power location work where * possible. */ public static final int POWER_LOW = 201; /** * Used with {@link #setQuality} to allow high power consumption for location. * *

This location request will allow high power location work. */ public static final int POWER_HIGH = 203; /** * By default, mFastestInterval = FASTEST_INTERVAL_MULTIPLE * mInterval */ private static final double FASTEST_INTERVAL_FACTOR = 6.0; // 6x private int mQuality = POWER_LOW; private long mInterval = 60 * 60 * 1000; // 60 minutes private long mFastestInterval = (long)(mInterval / FASTEST_INTERVAL_FACTOR); // 10 minutes private boolean mExplicitFastestInterval = false; private long mExpireAt = Long.MAX_VALUE; // no expiry private int mNumUpdates = Integer.MAX_VALUE; // no expiry private float mSmallestDisplacement = 0.0f; // meters private WorkSource mWorkSource = null; private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps private String mProvider = LocationManager.FUSED_PROVIDER; // for deprecated APIs that explicitly request a provider /** * Create a location request with default parameters. * *

Default parameters are for a low power, slowly updated location. * It can then be adjusted as required by the applications before passing * to the {@link LocationManager} * * @return a new location request */ public static LocationRequest create() { LocationRequest request = new LocationRequest(); return request; } /** @hide */ @SystemApi public static LocationRequest createFromDeprecatedProvider(String provider, long minTime, float minDistance, boolean singleShot) { if (minTime < 0) minTime = 0; if (minDistance < 0) minDistance = 0; int quality; if (LocationManager.PASSIVE_PROVIDER.equals(provider)) { quality = POWER_NONE; } else if (LocationManager.GPS_PROVIDER.equals(provider)) { quality = ACCURACY_FINE; } else { quality = POWER_LOW; } LocationRequest request = new LocationRequest() .setProvider(provider) .setQuality(quality) .setInterval(minTime) .setFastestInterval(minTime) .setSmallestDisplacement(minDistance); if (singleShot) request.setNumUpdates(1); return request; } /** @hide */ @SystemApi public static LocationRequest createFromDeprecatedCriteria(Criteria criteria, long minTime, float minDistance, boolean singleShot) { if (minTime < 0) minTime = 0; if (minDistance < 0) minDistance = 0; int quality; switch (criteria.getAccuracy()) { case Criteria.ACCURACY_COARSE: quality = ACCURACY_BLOCK; break; case Criteria.ACCURACY_FINE: quality = ACCURACY_FINE; break; default: { switch (criteria.getPowerRequirement()) { case Criteria.POWER_HIGH: quality = POWER_HIGH; break; default: quality = POWER_LOW; } } } LocationRequest request = new LocationRequest() .setQuality(quality) .setInterval(minTime) .setFastestInterval(minTime) .setSmallestDisplacement(minDistance); if (singleShot) request.setNumUpdates(1); return request; } /** @hide */ public LocationRequest() { } /** @hide */ public LocationRequest(LocationRequest src) { mQuality = src.mQuality; mInterval = src.mInterval; mFastestInterval = src.mFastestInterval; mExplicitFastestInterval = src.mExplicitFastestInterval; mExpireAt = src.mExpireAt; mNumUpdates = src.mNumUpdates; mSmallestDisplacement = src.mSmallestDisplacement; mProvider = src.mProvider; mWorkSource = src.mWorkSource; mHideFromAppOps = src.mHideFromAppOps; } /** * Set the quality of the request. * *

Use with a accuracy constant such as {@link #ACCURACY_FINE}, or a power * constant such as {@link #POWER_LOW}. You cannot request both and accuracy and * power, only one or the other can be specified. The system will then * maximize accuracy or minimize power as appropriate. * *

The quality of the request is a strong hint to the system for which * location sources to use. For example, {@link #ACCURACY_FINE} is more likely * to use GPS, and {@link #POWER_LOW} is more likely to use WIFI & Cell tower * positioning, but it also depends on many other factors (such as which sources * are available) and is implementation dependent. * *

{@link #setQuality} and {@link #setInterval} are the most important parameters * on a location request. * * @param quality an accuracy or power constant * @throws InvalidArgumentException if the quality constant is not valid * @return the same object, so that setters can be chained */ public LocationRequest setQuality(int quality) { checkQuality(quality); mQuality = quality; return this; } /** * Get the quality of the request. * * @return an accuracy or power constant */ public int getQuality() { return mQuality; } /** * Set the desired interval for active location updates, in milliseconds. * *

The location manager will actively try to obtain location updates * for your application at this interval, so it has a * direct influence on the amount of power used by your application. * Choose your interval wisely. * *

This interval is inexact. You may not receive updates at all (if * no location sources are available), or you may receive them * slower than requested. You may also receive them faster than * requested (if other applications are requesting location at a * faster interval). The fastest rate that you will receive * updates can be controlled with {@link #setFastestInterval}. * *

Applications with only the coarse location permission may have their * interval silently throttled. * *

An interval of 0 is allowed, but not recommended, since * location updates may be extremely fast on future implementations. * *

{@link #setQuality} and {@link #setInterval} are the most important parameters * on a location request. * * @param millis desired interval in millisecond, inexact * @throws InvalidArgumentException if the interval is less than zero * @return the same object, so that setters can be chained */ public LocationRequest setInterval(long millis) { checkInterval(millis); mInterval = millis; if (!mExplicitFastestInterval) { mFastestInterval = (long)(mInterval / FASTEST_INTERVAL_FACTOR); } return this; } /** * Get the desired interval of this request, in milliseconds. * * @return desired interval in milliseconds, inexact */ public long getInterval() { return mInterval; } /** * Explicitly set the fastest interval for location updates, in * milliseconds. * *

This controls the fastest rate at which your application will * receive location updates, which might be faster than * {@link #setInterval} in some situations (for example, if other * applications are triggering location updates). * *

This allows your application to passively acquire locations * at a rate faster than it actively acquires locations, saving power. * *

Unlike {@link #setInterval}, this parameter is exact. Your * application will never receive updates faster than this value. * *

If you don't call this method, a fastest interval * will be selected for you. It will be a value faster than your * active interval ({@link #setInterval}). * *

An interval of 0 is allowed, but not recommended, since * location updates may be extremely fast on future implementations. * *

If {@link #setFastestInterval} is set slower than {@link #setInterval}, * then your effective fastest interval is {@link #setInterval}. * * @param millis fastest interval for updates in milliseconds, exact * @throws InvalidArgumentException if the interval is less than zero * @return the same object, so that setters can be chained */ public LocationRequest setFastestInterval(long millis) { checkInterval(millis); mExplicitFastestInterval = true; mFastestInterval = millis; return this; } /** * Get the fastest interval of this request, in milliseconds. * *

The system will never provide location updates faster * than the minimum of {@link #getFastestInterval} and * {@link #getInterval}. * * @return fastest interval in milliseconds, exact */ public long getFastestInterval() { return mFastestInterval; } /** * Set the duration of this request, in milliseconds. * *

The duration begins immediately (and not when the request * is passed to the location manager), so call this method again * if the request is re-used at a later time. * *

The location manager will automatically stop updates after * the request expires. * *

The duration includes suspend time. Values less than 0 * are allowed, but indicate that the request has already expired. * * @param millis duration of request in milliseconds * @return the same object, so that setters can be chained */ public LocationRequest setExpireIn(long millis) { long elapsedRealtime = SystemClock.elapsedRealtime(); // Check for > Long.MAX_VALUE overflow (elapsedRealtime > 0): if (millis > Long.MAX_VALUE - elapsedRealtime) { mExpireAt = Long.MAX_VALUE; } else { mExpireAt = millis + elapsedRealtime; } if (mExpireAt < 0) mExpireAt = 0; return this; } /** * Set the request expiration time, in millisecond since boot. * *

This expiration time uses the same time base as {@link SystemClock#elapsedRealtime}. * *

The location manager will automatically stop updates after * the request expires. * *

The duration includes suspend time. Values before {@link SystemClock#elapsedRealtime} * are allowed, but indicate that the request has already expired. * * @param millis expiration time of request, in milliseconds since boot including suspend * @return the same object, so that setters can be chained */ public LocationRequest setExpireAt(long millis) { mExpireAt = millis; if (mExpireAt < 0) mExpireAt = 0; return this; } /** * Get the request expiration time, in milliseconds since boot. * *

This value can be compared to {@link SystemClock#elapsedRealtime} to determine * the time until expiration. * * @return expiration time of request, in milliseconds since boot including suspend */ public long getExpireAt() { return mExpireAt; } /** * Set the number of location updates. * *

By default locations are continuously updated until the request is explicitly * removed, however you can optionally request a set number of updates. * For example, if your application only needs a single fresh location, * then call this method with a value of 1 before passing the request * to the location manager. * * @param numUpdates the number of location updates requested * @throws InvalidArgumentException if numUpdates is 0 or less * @return the same object, so that setters can be chained */ public LocationRequest setNumUpdates(int numUpdates) { if (numUpdates <= 0) throw new IllegalArgumentException("invalid numUpdates: " + numUpdates); mNumUpdates = numUpdates; return this; } /** * Get the number of updates requested. * *

By default this is {@link Integer#MAX_VALUE}, which indicates that * locations are updated until the request is explicitly removed. * @return number of updates */ public int getNumUpdates() { return mNumUpdates; } /** @hide */ public void decrementNumUpdates() { if (mNumUpdates != Integer.MAX_VALUE) { mNumUpdates--; } if (mNumUpdates < 0) { mNumUpdates = 0; } } /** @hide */ @SystemApi public LocationRequest setProvider(String provider) { checkProvider(provider); mProvider = provider; return this; } /** @hide */ @SystemApi public String getProvider() { return mProvider; } /** @hide */ @SystemApi public LocationRequest setSmallestDisplacement(float meters) { checkDisplacement(meters); mSmallestDisplacement = meters; return this; } /** @hide */ @SystemApi public float getSmallestDisplacement() { return mSmallestDisplacement; } /** * Sets the WorkSource to use for power blaming of this location request. * *

No permissions are required to make this call, however the LocationManager * will throw a SecurityException when requesting location updates if the caller * doesn't have the {@link android.Manifest.permission#UPDATE_DEVICE_STATS} permission. * * @param workSource WorkSource defining power blame for this location request. * @hide */ @SystemApi public void setWorkSource(WorkSource workSource) { mWorkSource = workSource; } /** @hide */ @SystemApi public WorkSource getWorkSource() { return mWorkSource; } /** * Sets whether or not this location request should be hidden from AppOps. * *

Hiding a location request from AppOps will remove user visibility in the UI as to this * request's existence. It does not affect power blaming in the Battery page. * *

No permissions are required to make this call, however the LocationManager * will throw a SecurityException when requesting location updates if the caller * doesn't have the {@link android.Manifest.permission#UPDATE_APP_OPS_STATS} permission. * * @param hideFromAppOps If true AppOps won't keep track of this location request. * @see android.app.AppOpsManager * @hide */ @SystemApi public void setHideFromAppOps(boolean hideFromAppOps) { mHideFromAppOps = hideFromAppOps; } /** @hide */ @SystemApi public boolean getHideFromAppOps() { return mHideFromAppOps; } private static void checkInterval(long millis) { if (millis < 0) { throw new IllegalArgumentException("invalid interval: " + millis); } } private static void checkQuality(int quality) { switch (quality) { case ACCURACY_FINE: case ACCURACY_BLOCK: case ACCURACY_CITY: case POWER_NONE: case POWER_LOW: case POWER_HIGH: break; default: throw new IllegalArgumentException("invalid quality: " + quality); } } private static void checkDisplacement(float meters) { if (meters < 0.0f) { throw new IllegalArgumentException("invalid displacement: " + meters); } } private static void checkProvider(String name) { if (name == null) { throw new IllegalArgumentException("invalid provider: " + name); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public LocationRequest createFromParcel(Parcel in) { LocationRequest request = new LocationRequest(); request.setQuality(in.readInt()); request.setFastestInterval(in.readLong()); request.setInterval(in.readLong()); request.setExpireAt(in.readLong()); request.setNumUpdates(in.readInt()); request.setSmallestDisplacement(in.readFloat()); request.setHideFromAppOps(in.readInt() != 0); String provider = in.readString(); if (provider != null) request.setProvider(provider); WorkSource workSource = in.readParcelable(null); if (workSource != null) request.setWorkSource(workSource); return request; } @Override public LocationRequest[] newArray(int size) { return new LocationRequest[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mQuality); parcel.writeLong(mFastestInterval); parcel.writeLong(mInterval); parcel.writeLong(mExpireAt); parcel.writeInt(mNumUpdates); parcel.writeFloat(mSmallestDisplacement); parcel.writeInt(mHideFromAppOps ? 1 : 0); parcel.writeString(mProvider); parcel.writeParcelable(mWorkSource, 0); } /** @hide */ public static String qualityToString(int quality) { switch (quality) { case ACCURACY_FINE: return "ACCURACY_FINE"; case ACCURACY_BLOCK: return "ACCURACY_BLOCK"; case ACCURACY_CITY: return "ACCURACY_CITY"; case POWER_NONE: return "POWER_NONE"; case POWER_LOW: return "POWER_LOW"; case POWER_HIGH: return "POWER_HIGH"; default: return "???"; } } @Override public String toString() { StringBuilder s = new StringBuilder(); s.append("Request[").append(qualityToString(mQuality)); if (mProvider != null) s.append(' ').append(mProvider); if (mQuality != POWER_NONE) { s.append(" requested="); TimeUtils.formatDuration(mInterval, s); } s.append(" fastest="); TimeUtils.formatDuration(mFastestInterval, s); if (mExpireAt != Long.MAX_VALUE) { long expireIn = mExpireAt - SystemClock.elapsedRealtime(); s.append(" expireIn="); TimeUtils.formatDuration(expireIn, s); } if (mNumUpdates != Integer.MAX_VALUE){ s.append(" num=").append(mNumUpdates); } s.append(']'); return s.toString(); } }