/*
* 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 java.util.concurrent.TimeUnit.NANOSECONDS;
import android.annotation.SuppressLint;
import android.location.Location;
import android.os.Build.VERSION;
import android.os.Bundle;
import androidx.annotation.DoNotInline;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Helper for accessing features in {@link android.location.Location}.
*/
public final class LocationCompat {
/**
* Constant used as a key to store mock location status in {@link Location#getExtras()} for
* Android SDK levels below JBMR2 (18).
*/
@SuppressWarnings("ActionValue") // legacy value
public static final String EXTRA_IS_MOCK = "mockLocation";
/**
* Constant used as a key to store vertical accuracy in {@link Location#getExtras()} for
* Android SDK levels below Oreo (26).
*/
@SuppressWarnings("ActionValue") // legacy value
public static final String EXTRA_VERTICAL_ACCURACY = "verticalAccuracy";
/**
* Constant used as a key to store speed accuracy in {@link Location#getExtras()} for
* Android SDK levels below Oreo (26).
*/
@SuppressWarnings("ActionValue") // legacy value
public static final String EXTRA_SPEED_ACCURACY = "speedAccuracy";
/**
* Constant used as a key to store bearing accuracy in {@link Location#getExtras()} for
* Android SDK levels below Oreo (26).
*/
@SuppressWarnings("ActionValue") // legacy value
public static final String EXTRA_BEARING_ACCURACY = "bearingAccuracy";
/**
* Constant used as a key to store Mean Sea Level altitude in {@link Location#getExtras()}.
*/
public static final String EXTRA_MSL_ALTITUDE = "androidx.core.location.extra.MSL_ALTITUDE";
/**
* Constant used as a key to store Mean Sea Level altitude in {@link Location#getExtras()}.
*/
public static final String EXTRA_MSL_ALTITUDE_ACCURACY =
"androidx.core.location.extra.MSL_ALTITUDE_ACCURACY";
@Nullable
private static Method sSetIsFromMockProviderMethod;
@Nullable
private static Field sFieldsMaskField;
@Nullable
private static Integer sHasSpeedAccuracyMask;
@Nullable
private static Integer sHasBearingAccuracyMask;
@Nullable
private static Integer sHasVerticalAccuracyMask;
private LocationCompat() {
}
/**
* Return the time of this fix, in nanoseconds of elapsed real-time since system boot.
*
* <p>This value can be reliably compared to SystemClock.elapsedRealtimeNanos(), to calculate
* the age of a fix and to compare location fixes. This is reliable because elapsed real-time
* is guaranteed monotonic for each system boot and continues to increment even when the
* system is in deep sleep (unlike getTime().
*
* <p>All locations generated by the LocationManager are guaranteed to have a valid elapsed
* real-time.
*
* <p>NOTE: On API levels below 17, this method will attempt to provide an elapsed realtime
* based on the difference between system time and the location time. This should be taken as a
* best "guess" at what the elapsed realtime might have been, but if the clock used for
* location derivation is different from the system clock, the results may be inaccurate.
*/
public static long getElapsedRealtimeNanos(@NonNull Location location) {
return location.getElapsedRealtimeNanos();
}
/**
* Return the time of this fix, in milliseconds of elapsed real-time since system boot.
*
* @see #getElapsedRealtimeNanos(Location)
*/
public static long getElapsedRealtimeMillis(@NonNull Location location) {
return NANOSECONDS.toMillis(location.getElapsedRealtimeNanos());
}
/**
* Returns true if this location has a vertical accuracy.
*
* @see Location#hasVerticalAccuracy()
*/
public static boolean hasVerticalAccuracy(@NonNull Location location) {
if (VERSION.SDK_INT >= 26) {
return Api26Impl.hasVerticalAccuracy(location);
} else {
return containsExtra(location, EXTRA_VERTICAL_ACCURACY);
}
}
/**
* Get the estimated vertical accuracy of this location in meters.
*
* <p>NOTE: On API levels below 26, the concept of vertical accuracy does not exist. In order to
* allow for backwards compatibility and testing however, this method will attempt to read a
* float extra with the key {@link #EXTRA_VERTICAL_ACCURACY} and return the result.
*
* @see Location#getVerticalAccuracyMeters()
*/
public static float getVerticalAccuracyMeters(@NonNull Location location) {
if (VERSION.SDK_INT >= 26) {
return Api26Impl.getVerticalAccuracyMeters(location);
} else {
Bundle extras = location.getExtras();
if (extras == null) {
return 0.0f;
}
return extras.getFloat(EXTRA_VERTICAL_ACCURACY, 0.0f);
}
}
/**
* Set the estimated vertical accuracy of this location in meters.
*
* <p>NOTE: On API levels below 26, the concept of vertical accuracy does not exist. In order to
* allow for backwards compatibility and testing however, this method will attempt to set a
* float extra with the key {@link #EXTRA_VERTICAL_ACCURACY} to include vertical accuracy. Be
* aware that this will overwrite any prior extra value under the same key.
*
* @see Location#setVerticalAccuracyMeters(float)
*/
public static void setVerticalAccuracyMeters(@NonNull Location location,
float verticalAccuracyM) {
if (VERSION.SDK_INT >= 26) {
Api26Impl.setVerticalAccuracyMeters(location, verticalAccuracyM);
} else {
getOrCreateExtras(location).putFloat(EXTRA_VERTICAL_ACCURACY, verticalAccuracyM);
}
}
/**
* Removes the vertical accuracy from the location.
*/
public static void removeVerticalAccuracy(@NonNull Location location) {
if (VERSION.SDK_INT >= 33) {
Api33Impl.removeVerticalAccuracy(location);
} else if (VERSION.SDK_INT >= 29) {
Api29Impl.removeVerticalAccuracy(location);
} else if (VERSION.SDK_INT >= 28) {
Api28Impl.removeVerticalAccuracy(location);
} else if (VERSION.SDK_INT >= 26) {
Api26Impl.removeVerticalAccuracy(location);
} else {
removeExtra(location, EXTRA_VERTICAL_ACCURACY);
}
}
/**
* Returns true if this location has a speed accuracy.
*
* @see Location#hasSpeedAccuracy()
*/
public static boolean hasSpeedAccuracy(@NonNull Location location) {
if (VERSION.SDK_INT >= 26) {
return Api26Impl.hasSpeedAccuracy(location);
} else {
return containsExtra(location, EXTRA_SPEED_ACCURACY);
}
}
/**
* Get the estimated speed accuracy of this location in meters per second.
*
* <p>NOTE: On API levels below 26, the concept of speed accuracy does not exist. In order to
* allow for backwards compatibility and testing however, this method will attempt to read a
* float extra with the key {@link #EXTRA_SPEED_ACCURACY} and return the result.
*
* @see Location#getSpeedAccuracyMetersPerSecond()
*/
public static float getSpeedAccuracyMetersPerSecond(@NonNull Location location) {
if (VERSION.SDK_INT >= 26) {
return Api26Impl.getSpeedAccuracyMetersPerSecond(location);
} else {
Bundle extras = location.getExtras();
if (extras == null) {
return 0.0f;
}
return extras.getFloat(EXTRA_SPEED_ACCURACY, 0.0f);
}
}
/**
* Set the estimated speed accuracy of this location in meters per second.
*
* <p>NOTE: On API levels below 26, the concept of speed accuracy does not exist. In order to
* allow for backwards compatibility and testing however, this method will attempt to set a
* float extra with the key {@link #EXTRA_SPEED_ACCURACY} to include speed accuracy. Be
* aware that this will overwrite any prior extra value under the same key.
*
* @see Location#setSpeedAccuracyMetersPerSecond(float)
*/
public static void setSpeedAccuracyMetersPerSecond(@NonNull Location location,
float speedAccuracyMps) {
if (VERSION.SDK_INT >= 26) {
Api26Impl.setSpeedAccuracyMetersPerSecond(location, speedAccuracyMps);
} else {
getOrCreateExtras(location).putFloat(EXTRA_SPEED_ACCURACY, speedAccuracyMps);
}
}
/**
* Removes the speed accuracy from the location.
*/
public static void removeSpeedAccuracy(@NonNull Location location) {
if (VERSION.SDK_INT >= 33) {
Api33Impl.removeSpeedAccuracy(location);
} else if (VERSION.SDK_INT >= 29) {
Api29Impl.removeSpeedAccuracy(location);
} else if (VERSION.SDK_INT >= 28) {
Api28Impl.removeSpeedAccuracy(location);
} else if (VERSION.SDK_INT >= 26) {
Api26Impl.removeSpeedAccuracy(location);
} else {
removeExtra(location, EXTRA_SPEED_ACCURACY);
}
}
/**
* Returns true if this location has a bearing accuracy.
*
* @see Location#hasBearingAccuracy()
*/
public static boolean hasBearingAccuracy(@NonNull Location location) {
if (VERSION.SDK_INT >= 26) {
return Api26Impl.hasBearingAccuracy(location);
} else {
return containsExtra(location, EXTRA_BEARING_ACCURACY);
}
}
/**
* Get the estimated bearing accuracy of this location in degrees.
*
* <p>NOTE: On API levels below 26, the concept of bearing accuracy does not exist. In order to
* allow for backwards compatibility and testing however, this method will attempt to read a
* float extra with the key {@link #EXTRA_BEARING_ACCURACY} and return the result.
*
* @see Location#getBearingAccuracyDegrees()
*/
public static float getBearingAccuracyDegrees(@NonNull Location location) {
if (VERSION.SDK_INT >= 26) {
return Api26Impl.getBearingAccuracyDegrees(location);
} else {
Bundle extras = location.getExtras();
if (extras == null) {
return 0.0f;
}
return extras.getFloat(EXTRA_BEARING_ACCURACY, 0.0f);
}
}
/**
* Set the estimated bearing accuracy of this location in degrees.
*
* <p>NOTE: On API levels below 26, the concept of bearing accuracy does not exist. In order to
* allow for backwards compatibility and testing however, this method will attempt to set a
* float extra with the key {@link #EXTRA_BEARING_ACCURACY} to include bearing accuracy. Be
* aware that this will overwrite any prior extra value under the same key.
*
* @see Location#setBearingAccuracyDegrees(float)
*/
public static void setBearingAccuracyDegrees(@NonNull Location location,
float bearingAccuracyD) {
if (VERSION.SDK_INT >= 26) {
Api26Impl.setBearingAccuracyDegrees(location, bearingAccuracyD);
} else {
getOrCreateExtras(location).putFloat(EXTRA_BEARING_ACCURACY, bearingAccuracyD);
}
}
/**
* Removes the bearing accuracy from the location.
*/
public static void removeBearingAccuracy(@NonNull Location location) {
if (VERSION.SDK_INT >= 33) {
Api33Impl.removeBearingAccuracy(location);
} else if (VERSION.SDK_INT >= 29) {
Api29Impl.removeBearingAccuracy(location);
} else if (VERSION.SDK_INT >= 28) {
Api28Impl.removeBearingAccuracy(location);
} else if (VERSION.SDK_INT >= 26) {
Api26Impl.removeBearingAccuracy(location);
} else {
removeExtra(location, EXTRA_BEARING_ACCURACY);
}
}
/**
* Returns the Mean Sea Level altitude of the location in meters.
*
* <p>This is only valid if {@link #hasMslAltitude(Location)} is true.
*
* <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude does not exist. In
* order to allow for backwards compatibility and testing however, this method will attempt
* to read a double extra with the key {@link #EXTRA_MSL_ALTITUDE} and return the result.
*
* @see Location#getMslAltitudeMeters()
*/
public static double getMslAltitudeMeters(@NonNull Location location) {
if (VERSION.SDK_INT >= 34) {
return Api34Impl.getMslAltitudeMeters(location);
}
return getOrCreateExtras(location).getDouble(EXTRA_MSL_ALTITUDE);
}
/**
* Sets the Mean Sea Level altitude of the location in meters.
*
* <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude does not exist. In
* order to allow for backwards compatibility and testing however, this method will attempt
* to set a double extra with the key {@link #EXTRA_MSL_ALTITUDE} to include Mean Sea Level
* altitude. Be aware that this will overwrite any prior extra value under the same key.
*
* @see Location#setMslAltitudeMeters(double)
*/
public static void setMslAltitudeMeters(@NonNull Location location,
double mslAltitudeMeters) {
if (VERSION.SDK_INT >= 34) {
Api34Impl.setMslAltitudeMeters(location, mslAltitudeMeters);
} else {
getOrCreateExtras(location).putDouble(EXTRA_MSL_ALTITUDE, mslAltitudeMeters);
}
}
/**
* Returns true if the location has a Mean Sea Level altitude, false otherwise.
*
* <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude does not exist. In
* order to allow for backwards compatibility and testing however, this method will return
* true if an extra value is with the key {@link #EXTRA_MSL_ALTITUDE}.
*
* @see Location#hasMslAltitude()
*/
public static boolean hasMslAltitude(@NonNull Location location) {
if (VERSION.SDK_INT >= 34) {
return Api34Impl.hasMslAltitude(location);
}
return containsExtra(location, EXTRA_MSL_ALTITUDE);
}
/**
* Removes the Mean Sea Level altitude from the location.
*
* <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude does not exist. In
* order to allow for backwards compatibility and testing however, this method will attempt
* to remove any extra value with the key {@link #EXTRA_MSL_ALTITUDE}.
*
* @see Location#removeMslAltitude()
*/
public static void removeMslAltitude(@NonNull Location location) {
if (VERSION.SDK_INT >= 34) {
Api34Impl.removeMslAltitude(location);
} else {
removeExtra(location, EXTRA_MSL_ALTITUDE);
}
}
/**
* Returns the estimated Mean Sea Level altitude accuracy in meters of the location at the 68th
* percentile confidence level. This means that there is 68% chance that the true Mean Sea Level
* altitude of the location falls within {@link #getMslAltitudeMeters(Location)} +/- this
* uncertainty.
*
* <p>This is only valid if {@link #hasMslAltitudeAccuracy(Location)} is true.
*
* <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude accuracy does not
* exist. In order to allow for backwards compatibility and testing however, this method will
* attempt to read a float extra with the key {@link #EXTRA_MSL_ALTITUDE_ACCURACY} and return
* the result.
*
* @see Location#getMslAltitudeAccuracyMeters()
*/
public static @FloatRange(from = 0.0) float getMslAltitudeAccuracyMeters(
@NonNull Location location) {
if (VERSION.SDK_INT >= 34) {
return Api34Impl.getMslAltitudeAccuracyMeters(location);
}
return getOrCreateExtras(location).getFloat(EXTRA_MSL_ALTITUDE_ACCURACY);
}
/**
* Sets the Mean Sea Level altitude accuracy of the location in meters.
*
* <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude accuracy does not
* exist. In order to allow for backwards compatibility and testing however, this method will
* attempt to set a float extra with the key {@link #EXTRA_MSL_ALTITUDE_ACCURACY} to include
* Mean Sea Level altitude accuracy. Be aware that this will overwrite any prior extra value
* under the same key.
*
* @see Location#setMslAltitudeAccuracyMeters(float)
*/
public static void setMslAltitudeAccuracyMeters(@NonNull Location location,
@FloatRange(from = 0.0) float mslAltitudeAccuracyMeters) {
if (VERSION.SDK_INT >= 34) {
Api34Impl.setMslAltitudeAccuracyMeters(location, mslAltitudeAccuracyMeters);
} else {
getOrCreateExtras(location).putFloat(EXTRA_MSL_ALTITUDE_ACCURACY,
mslAltitudeAccuracyMeters);
}
}
/**
* Returns true if the location has a Mean Sea Level altitude accuracy, false otherwise.
*
* <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude accuracy does not
* exist. In order to allow for backwards compatibility and testing however, this method will
* return true if an extra value is with the key {@link #EXTRA_MSL_ALTITUDE_ACCURACY}.
*
* @see Location#hasMslAltitudeAccuracy()
*/
public static boolean hasMslAltitudeAccuracy(@NonNull Location location) {
if (VERSION.SDK_INT >= 34) {
return Api34Impl.hasMslAltitudeAccuracy(location);
}
return containsExtra(location, EXTRA_MSL_ALTITUDE_ACCURACY);
}
/**
* Removes the Mean Sea Level altitude accuracy from the location.
*
* <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude accuracy does not
* exist. In order to allow for backwards compatibility and testing however, this method will
* attempt to remove any extra value with the key {@link #EXTRA_MSL_ALTITUDE_ACCURACY}.
*
* @see Location#removeMslAltitudeAccuracy()
*/
public static void removeMslAltitudeAccuracy(@NonNull Location location) {
if (VERSION.SDK_INT >= 34) {
Api34Impl.removeMslAltitudeAccuracy(location);
} else {
removeExtra(location, EXTRA_MSL_ALTITUDE_ACCURACY);
}
}
/**
* Returns true if this location is marked as a mock location. If this location comes from the
* Android framework, this indicates that the location was provided by a test location provider,
* and thus may not be related to the actual location of the device.
*
* <p>NOTE: On API levels below 18, the concept of a mock location does not exist. In order to
* allow for backwards compatibility and testing however, this method will attempt to read a
* boolean extra with the key {@link #EXTRA_IS_MOCK} and use the result to determine whether
* this should be considered a mock location.
*
* @see android.location.LocationManager#addTestProvider
*/
public static boolean isMock(@NonNull Location location) {
return location.isFromMockProvider();
}
/**
* Sets whether this location is marked as a mock location.
*
* <p>NOTE: On API levels below 18, the concept of a mock location does not exist. In order to
* allow for backwards compatibility and testing however, this method will attempt to set a
* boolean extra with the key {@link #EXTRA_IS_MOCK} to mark the location as mock. Be aware that
* this will overwrite any prior extra value under the same key.
*/
@SuppressLint("BanUncheckedReflection")
public static void setMock(@NonNull Location location, boolean mock) {
try {
getSetIsFromMockProviderMethod().invoke(location, mock);
} catch (NoSuchMethodException e) {
Error error = new NoSuchMethodError();
error.initCause(e);
throw error;
} catch (IllegalAccessException e) {
Error error = new IllegalAccessError();
error.initCause(e);
throw error;
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@RequiresApi(34)
private static class Api34Impl {
private Api34Impl() {
}
@DoNotInline
static double getMslAltitudeMeters(Location location) {
return location.getMslAltitudeMeters();
}
@DoNotInline
static void setMslAltitudeMeters(Location location, double mslAltitudeMeters) {
location.setMslAltitudeMeters(mslAltitudeMeters);
}
@DoNotInline
static boolean hasMslAltitude(Location location) {
return location.hasMslAltitude();
}
@DoNotInline
static void removeMslAltitude(Location location) {
location.removeMslAltitude();
}
@DoNotInline
static float getMslAltitudeAccuracyMeters(Location location) {
return location.getMslAltitudeAccuracyMeters();
}
@DoNotInline
static void setMslAltitudeAccuracyMeters(Location location,
float mslAltitudeAccuracyMeters) {
location.setMslAltitudeAccuracyMeters(mslAltitudeAccuracyMeters);
}
@DoNotInline
static boolean hasMslAltitudeAccuracy(Location location) {
return location.hasMslAltitudeAccuracy();
}
@DoNotInline
static void removeMslAltitudeAccuracy(Location location) {
location.removeMslAltitudeAccuracy();
}
}
@RequiresApi(33)
private static class Api33Impl {
private Api33Impl() {}
@DoNotInline
static void removeVerticalAccuracy(Location location) {
location.removeVerticalAccuracy();
}
@DoNotInline
static void removeSpeedAccuracy(Location location) {
location.removeSpeedAccuracy();
}
@DoNotInline
static void removeBearingAccuracy(Location location) {
location.removeBearingAccuracy();
}
}
@RequiresApi(29)
private static class Api29Impl {
private Api29Impl() {}
@DoNotInline
static void removeVerticalAccuracy(Location location) {
if (!location.hasVerticalAccuracy()) {
return;
}
// reflection of non-SDK APIs doesn't work on P+, fallback to resetting the location
double elapsedRealtimeUncertaintyNs = location.getElapsedRealtimeUncertaintyNanos();
Api28Impl.removeVerticalAccuracy(location);
location.setElapsedRealtimeUncertaintyNanos(elapsedRealtimeUncertaintyNs);
}
@DoNotInline
static void removeSpeedAccuracy(Location location) {
if (!location.hasSpeedAccuracy()) {
return;
}
// reflection of non-SDK APIs doesn't work on P+, fallback to resetting the location
double elapsedRealtimeUncertaintyNs = location.getElapsedRealtimeUncertaintyNanos();
Api28Impl.removeSpeedAccuracy(location);
location.setElapsedRealtimeUncertaintyNanos(elapsedRealtimeUncertaintyNs);
}
@DoNotInline
static void removeBearingAccuracy(Location location) {
if (!location.hasBearingAccuracy()) {
return;
}
// reflection of non-SDK APIs doesn't work on P+, fallback to resetting the location
double elapsedRealtimeUncertaintyNs = location.getElapsedRealtimeUncertaintyNanos();
Api28Impl.removeBearingAccuracy(location);
location.setElapsedRealtimeUncertaintyNanos(elapsedRealtimeUncertaintyNs);
}
}
@RequiresApi(28)
private static class Api28Impl {
private Api28Impl() {}
@DoNotInline
static void removeVerticalAccuracy(Location location) {
if (!location.hasVerticalAccuracy()) {
return;
}
// reflection of non-SDK APIs doesn't work on P+, fallback to resetting the location
// (which unfortunately means a new Bundle will be created)
String provider = location.getProvider();
long time = location.getTime();
long elapsedRealtimeNs = location.getElapsedRealtimeNanos();
double latitude = location.getLatitude();
double longitude = location.getLongitude();
boolean hasAltitude = location.hasAltitude();
double altitude = location.getAltitude();
boolean hasSpeed = location.hasSpeed();
float speed = location.getSpeed();
boolean hasBearing = location.hasBearing();
float bearing = location.getBearing();
boolean hasAccuracy = location.hasAccuracy();
float accuracy = location.getAccuracy();
boolean hasSpeedAccuracy = location.hasSpeedAccuracy();
float speedAccuracy = location.getSpeedAccuracyMetersPerSecond();
boolean hasBearingAccuracy = location.hasBearingAccuracy();
float bearingAccuracy = location.getBearingAccuracyDegrees();
Bundle extras = location.getExtras();
location.reset();
location.setProvider(provider);
location.setTime(time);
location.setElapsedRealtimeNanos(elapsedRealtimeNs);
location.setLatitude(latitude);
location.setLongitude(longitude);
if (hasAltitude) {
location.setAltitude(altitude);
}
if (hasSpeed) {
location.setSpeed(speed);
}
if (hasBearing) {
location.setBearing(bearing);
}
if (hasAccuracy) {
location.setAccuracy(accuracy);
}
if (hasSpeedAccuracy) {
location.setSpeedAccuracyMetersPerSecond(speedAccuracy);
}
if (hasBearingAccuracy) {
location.setBearingAccuracyDegrees(bearingAccuracy);
}
if (extras != null) {
location.setExtras(extras);
}
}
@DoNotInline
static void removeSpeedAccuracy(Location location) {
if (!location.hasSpeedAccuracy()) {
return;
}
// reflection of non-SDK APIs doesn't work on P+, fallback to resetting the location
// (which unfortunately means a new Bundle will be created)
String provider = location.getProvider();
long time = location.getTime();
long elapsedRealtimeNs = location.getElapsedRealtimeNanos();
double latitude = location.getLatitude();
double longitude = location.getLongitude();
boolean hasAltitude = location.hasAltitude();
double altitude = location.getAltitude();
boolean hasSpeed = location.hasSpeed();
float speed = location.getSpeed();
boolean hasBearing = location.hasBearing();
float bearing = location.getBearing();
boolean hasAccuracy = location.hasAccuracy();
float accuracy = location.getAccuracy();
boolean hasVerticalAccuracy = location.hasVerticalAccuracy();
float verticalAccuracy = location.getVerticalAccuracyMeters();
boolean hasBearingAccuracy = location.hasBearingAccuracy();
float bearingAccuracy = location.getBearingAccuracyDegrees();
Bundle extras = location.getExtras();
location.reset();
location.setProvider(provider);
location.setTime(time);
location.setElapsedRealtimeNanos(elapsedRealtimeNs);
location.setLatitude(latitude);
location.setLongitude(longitude);
if (hasAltitude) {
location.setAltitude(altitude);
}
if (hasSpeed) {
location.setSpeed(speed);
}
if (hasBearing) {
location.setBearing(bearing);
}
if (hasAccuracy) {
location.setAccuracy(accuracy);
}
if (hasVerticalAccuracy) {
location.setVerticalAccuracyMeters(verticalAccuracy);
}
if (hasBearingAccuracy) {
location.setBearingAccuracyDegrees(bearingAccuracy);
}
if (extras != null) {
location.setExtras(extras);
}
}
@DoNotInline
static void removeBearingAccuracy(Location location) {
if (!location.hasBearingAccuracy()) {
return;
}
// reflection of non-SDK APIs doesn't work on P+, fallback to resetting the location
// (which unfortunately means a new Bundle will be created)
String provider = location.getProvider();
long time = location.getTime();
long elapsedRealtimeNs = location.getElapsedRealtimeNanos();
double latitude = location.getLatitude();
double longitude = location.getLongitude();
boolean hasAltitude = location.hasAltitude();
double altitude = location.getAltitude();
boolean hasSpeed = location.hasSpeed();
float speed = location.getSpeed();
boolean hasBearing = location.hasBearing();
float bearing = location.getBearing();
boolean hasAccuracy = location.hasAccuracy();
float accuracy = location.getAccuracy();
boolean hasVerticalAccuracy = location.hasVerticalAccuracy();
float verticalAccuracy = location.getVerticalAccuracyMeters();
boolean hasSpeedAccuracy = location.hasSpeedAccuracy();
float speedAccuracy = location.getSpeedAccuracyMetersPerSecond();
Bundle extras = location.getExtras();
location.reset();
location.setProvider(provider);
location.setTime(time);
location.setElapsedRealtimeNanos(elapsedRealtimeNs);
location.setLatitude(latitude);
location.setLongitude(longitude);
if (hasAltitude) {
location.setAltitude(altitude);
}
if (hasSpeed) {
location.setSpeed(speed);
}
if (hasBearing) {
location.setBearing(bearing);
}
if (hasAccuracy) {
location.setAccuracy(accuracy);
}
if (hasVerticalAccuracy) {
location.setVerticalAccuracyMeters(verticalAccuracy);
}
if (hasSpeedAccuracy) {
location.setBearingAccuracyDegrees(speedAccuracy);
}
if (extras != null) {
location.setExtras(extras);
}
}
}
@RequiresApi(26)
private static class Api26Impl {
private Api26Impl() {
}
@DoNotInline
static boolean hasVerticalAccuracy(Location location) {
return location.hasVerticalAccuracy();
}
@DoNotInline
static float getVerticalAccuracyMeters(Location location) {
return location.getVerticalAccuracyMeters();
}
@DoNotInline
static void setVerticalAccuracyMeters(Location location, float verticalAccuracyM) {
location.setVerticalAccuracyMeters(verticalAccuracyM);
}
@DoNotInline
static void removeVerticalAccuracy(Location location) {
try {
byte fieldsMask = getFieldsMaskField().getByte(location);
fieldsMask = (byte) (fieldsMask & ~getHasVerticalAccuracyMask());
getFieldsMaskField().setByte(location, fieldsMask);
} catch (NoSuchFieldException | IllegalAccessException e) {
Error error = new IllegalAccessError();
error.initCause(e);
throw error;
}
}
@DoNotInline
static boolean hasSpeedAccuracy(Location location) {
return location.hasSpeedAccuracy();
}
@DoNotInline
static float getSpeedAccuracyMetersPerSecond(Location location) {
return location.getSpeedAccuracyMetersPerSecond();
}
@DoNotInline
static void setSpeedAccuracyMetersPerSecond(Location location, float speedAccuracyMps) {
location.setSpeedAccuracyMetersPerSecond(speedAccuracyMps);
}
@DoNotInline
static void removeSpeedAccuracy(Location location) {
try {
byte fieldsMask = getFieldsMaskField().getByte(location);
fieldsMask = (byte) (fieldsMask & ~getHasSpeedAccuracyMask());
getFieldsMaskField().setByte(location, fieldsMask);
} catch (NoSuchFieldException e) {
Error error = new NoSuchFieldError();
error.initCause(e);
throw error;
} catch (IllegalAccessException e) {
Error error = new IllegalAccessError();
error.initCause(e);
throw error;
}
}
@DoNotInline
static boolean hasBearingAccuracy(Location location) {
return location.hasBearingAccuracy();
}
@DoNotInline
static float getBearingAccuracyDegrees(Location location) {
return location.getBearingAccuracyDegrees();
}
@DoNotInline
static void setBearingAccuracyDegrees(Location location, float bearingAccuracyD) {
location.setBearingAccuracyDegrees(bearingAccuracyD);
}
@DoNotInline
static void removeBearingAccuracy(Location location) {
try {
byte fieldsMask = getFieldsMaskField().getByte(location);
fieldsMask = (byte) (fieldsMask & ~getHasBearingAccuracyMask());
getFieldsMaskField().setByte(location, fieldsMask);
} catch (NoSuchFieldException e) {
Error error = new NoSuchFieldError();
error.initCause(e);
throw error;
} catch (IllegalAccessException e) {
Error error = new IllegalAccessError();
error.initCause(e);
throw error;
}
}
}
private static Method getSetIsFromMockProviderMethod() throws NoSuchMethodException {
if (sSetIsFromMockProviderMethod == null) {
sSetIsFromMockProviderMethod = Location.class.getDeclaredMethod("setIsFromMockProvider",
boolean.class);
sSetIsFromMockProviderMethod.setAccessible(true);
}
return sSetIsFromMockProviderMethod;
}
@SuppressLint("BlockedPrivateApi")
static Field getFieldsMaskField() throws NoSuchFieldException {
if (sFieldsMaskField == null) {
sFieldsMaskField = Location.class.getDeclaredField("mFieldsMask");
sFieldsMaskField.setAccessible(true);
}
return sFieldsMaskField;
}
@SuppressLint("SoonBlockedPrivateApi")
static int getHasSpeedAccuracyMask() throws NoSuchFieldException,
IllegalAccessException {
if (sHasSpeedAccuracyMask == null) {
Field hasSpeedAccuracyMaskField = Location.class.getDeclaredField(
"HAS_SPEED_ACCURACY_MASK");
hasSpeedAccuracyMaskField.setAccessible(true);
sHasSpeedAccuracyMask = hasSpeedAccuracyMaskField.getInt(null);
}
return sHasSpeedAccuracyMask;
}
@SuppressLint("SoonBlockedPrivateApi")
static int getHasBearingAccuracyMask()
throws NoSuchFieldException, IllegalAccessException {
if (sHasBearingAccuracyMask == null) {
Field hasBearingAccuracyMaskField = Location.class.getDeclaredField(
"HAS_BEARING_ACCURACY_MASK");
hasBearingAccuracyMaskField.setAccessible(true);
sHasBearingAccuracyMask = hasBearingAccuracyMaskField.getInt(null);
}
return sHasBearingAccuracyMask;
}
@SuppressLint("SoonBlockedPrivateApi")
static int getHasVerticalAccuracyMask()
throws NoSuchFieldException, IllegalAccessException {
if (sHasVerticalAccuracyMask == null) {
Field hasVerticalAccuracyMaskField = Location.class.getDeclaredField(
"HAS_VERTICAL_ACCURACY_MASK");
hasVerticalAccuracyMaskField.setAccessible(true);
sHasVerticalAccuracyMask = hasVerticalAccuracyMaskField.getInt(null);
}
return sHasVerticalAccuracyMask;
}
private static Bundle getOrCreateExtras(@NonNull Location location) {
Bundle extras = location.getExtras();
if (extras == null) {
location.setExtras(new Bundle());
extras = location.getExtras();
}
return extras;
}
private static boolean containsExtra(@NonNull Location location, String key) {
Bundle extras = location.getExtras();
return extras != null && extras.containsKey(key);
}
private static void removeExtra(@NonNull Location location, String key) {
Bundle extras = location.getExtras();
if (extras != null) {
extras.remove(key);
if (extras.isEmpty()) {
location.setExtras(null);
}
}
}
}