diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
commit | 10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch) | |
tree | 8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/location | |
parent | 677516fb6b6f207d373984757d3d9450474b6b00 (diff) | |
download | android-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz |
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \
--bid 4335822 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4335822.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/location')
33 files changed, 12386 insertions, 0 deletions
diff --git a/android/location/Address.java b/android/location/Address.java new file mode 100644 index 00000000..335ad5eb --- /dev/null +++ b/android/location/Address.java @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2007 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 java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class representing an Address, i.e, a set of Strings describing a location. + * + * The address format is a simplified version of xAL (eXtensible Address Language) + * http://www.oasis-open.org/committees/ciq/ciq.html#6 + */ +public class Address implements Parcelable { + + private Locale mLocale; + + private String mFeatureName; + private HashMap<Integer, String> mAddressLines; + private int mMaxAddressLineIndex = -1; + private String mAdminArea; + private String mSubAdminArea; + private String mLocality; + private String mSubLocality; + private String mThoroughfare; + private String mSubThoroughfare; + private String mPremises; + private String mPostalCode; + private String mCountryCode; + private String mCountryName; + private double mLatitude; + private double mLongitude; + private boolean mHasLatitude = false; + private boolean mHasLongitude = false; + private String mPhone; + private String mUrl; + private Bundle mExtras = null; + + /** + * Constructs a new Address object set to the given Locale and with all + * other fields initialized to null or false. + */ + public Address(Locale locale) { + mLocale = locale; + } + + /** + * Returns the Locale associated with this address. + */ + public Locale getLocale() { + return mLocale; + } + + /** + * Returns the largest index currently in use to specify an address line. + * If no address lines are specified, -1 is returned. + */ + public int getMaxAddressLineIndex() { + return mMaxAddressLineIndex; + } + + /** + * Returns a line of the address numbered by the given index + * (starting at 0), or null if no such line is present. + * + * @throws IllegalArgumentException if index < 0 + */ + public String getAddressLine(int index) { + if (index < 0) { + throw new IllegalArgumentException("index = " + index + " < 0"); + } + return mAddressLines == null? null : mAddressLines.get(index); + } + + /** + * Sets the line of the address numbered by index (starting at 0) to the + * given String, which may be null. + * + * @throws IllegalArgumentException if index < 0 + */ + public void setAddressLine(int index, String line) { + if (index < 0) { + throw new IllegalArgumentException("index = " + index + " < 0"); + } + if (mAddressLines == null) { + mAddressLines = new HashMap<Integer, String>(); + } + mAddressLines.put(index, line); + + if (line == null) { + // We've eliminated a line, recompute the max index + mMaxAddressLineIndex = -1; + for (Integer i : mAddressLines.keySet()) { + mMaxAddressLineIndex = Math.max(mMaxAddressLineIndex, i); + } + } else { + mMaxAddressLineIndex = Math.max(mMaxAddressLineIndex, index); + } + } + + /** + * Returns the feature name of the address, for example, "Golden Gate Bridge", or null + * if it is unknown + */ + public String getFeatureName() { + return mFeatureName; + } + + /** + * Sets the feature name of the address to the given String, which may be null + */ + public void setFeatureName(String featureName) { + mFeatureName = featureName; + } + + /** + * Returns the administrative area name of the address, for example, "CA", or null if + * it is unknown + */ + public String getAdminArea() { + return mAdminArea; + } + + /** + * Sets the administrative area name of the address to the given String, which may be null + */ + public void setAdminArea(String adminArea) { + this.mAdminArea = adminArea; + } + + /** + * Returns the sub-administrative area name of the address, for example, "Santa Clara County", + * or null if it is unknown + */ + public String getSubAdminArea() { + return mSubAdminArea; + } + + /** + * Sets the sub-administrative area name of the address to the given String, which may be null + */ + public void setSubAdminArea(String subAdminArea) { + this.mSubAdminArea = subAdminArea; + } + + /** + * Returns the locality of the address, for example "Mountain View", or null if it is unknown. + */ + public String getLocality() { + return mLocality; + } + + /** + * Sets the locality of the address to the given String, which may be null. + */ + public void setLocality(String locality) { + mLocality = locality; + } + + /** + * Returns the sub-locality of the address, or null if it is unknown. + * For example, this may correspond to the neighborhood of the locality. + */ + public String getSubLocality() { + return mSubLocality; + } + + /** + * Sets the sub-locality of the address to the given String, which may be null. + */ + public void setSubLocality(String sublocality) { + mSubLocality = sublocality; + } + + /** + * Returns the thoroughfare name of the address, for example, "1600 Ampitheater Parkway", + * which may be null + */ + public String getThoroughfare() { + return mThoroughfare; + } + + /** + * Sets the thoroughfare name of the address, which may be null. + */ + public void setThoroughfare(String thoroughfare) { + this.mThoroughfare = thoroughfare; + } + + /** + * Returns the sub-thoroughfare name of the address, which may be null. + * This may correspond to the street number of the address. + */ + public String getSubThoroughfare() { + return mSubThoroughfare; + } + + /** + * Sets the sub-thoroughfare name of the address, which may be null. + */ + public void setSubThoroughfare(String subthoroughfare) { + this.mSubThoroughfare = subthoroughfare; + } + + /** + * Returns the premises of the address, or null if it is unknown. + */ + public String getPremises() { + return mPremises; + } + + /** + * Sets the premises of the address to the given String, which may be null. + */ + public void setPremises(String premises) { + mPremises = premises; + } + + /** + * Returns the postal code of the address, for example "94110", + * or null if it is unknown. + */ + public String getPostalCode() { + return mPostalCode; + } + + /** + * Sets the postal code of the address to the given String, which may + * be null. + */ + public void setPostalCode(String postalCode) { + mPostalCode = postalCode; + } + + /** + * Returns the country code of the address, for example "US", + * or null if it is unknown. + */ + public String getCountryCode() { + return mCountryCode; + } + + /** + * Sets the country code of the address to the given String, which may + * be null. + */ + public void setCountryCode(String countryCode) { + mCountryCode = countryCode; + } + + /** + * Returns the localized country name of the address, for example "Iceland", + * or null if it is unknown. + */ + public String getCountryName() { + return mCountryName; + } + + /** + * Sets the country name of the address to the given String, which may + * be null. + */ + public void setCountryName(String countryName) { + mCountryName = countryName; + } + + /** + * Returns true if a latitude has been assigned to this Address, + * false otherwise. + */ + public boolean hasLatitude() { + return mHasLatitude; + } + + /** + * Returns the latitude of the address if known. + * + * @throws IllegalStateException if this Address has not been assigned + * a latitude. + */ + public double getLatitude() { + if (mHasLatitude) { + return mLatitude; + } else { + throw new IllegalStateException(); + } + } + + /** + * Sets the latitude associated with this address. + */ + public void setLatitude(double latitude) { + mLatitude = latitude; + mHasLatitude = true; + } + + /** + * Removes any latitude associated with this address. + */ + public void clearLatitude() { + mHasLatitude = false; + } + + /** + * Returns true if a longitude has been assigned to this Address, + * false otherwise. + */ + public boolean hasLongitude() { + return mHasLongitude; + } + + /** + * Returns the longitude of the address if known. + * + * @throws IllegalStateException if this Address has not been assigned + * a longitude. + */ + public double getLongitude() { + if (mHasLongitude) { + return mLongitude; + } else { + throw new IllegalStateException(); + } + } + + /** + * Sets the longitude associated with this address. + */ + public void setLongitude(double longitude) { + mLongitude = longitude; + mHasLongitude = true; + } + + /** + * Removes any longitude associated with this address. + */ + public void clearLongitude() { + mHasLongitude = false; + } + + /** + * Returns the phone number of the address if known, + * or null if it is unknown. + * + * @throws IllegalStateException if this Address has not been assigned + * a latitude. + */ + public String getPhone() { + return mPhone; + } + + /** + * Sets the phone number associated with this address. + */ + public void setPhone(String phone) { + mPhone = phone; + } + + /** + * Returns the public URL for the address if known, + * or null if it is unknown. + */ + public String getUrl() { + return mUrl; + } + + /** + * Sets the public URL associated with this address. + */ + public void setUrl(String Url) { + mUrl = Url; + } + + /** + * Returns additional provider-specific information about the + * address as a Bundle. The keys and values are determined + * by the provider. If no additional information is available, + * null is returned. + * + * <!-- + * <p> A number of common key/value pairs are listed + * below. Providers that use any of the keys on this list must + * provide the corresponding value as described below. + * + * <ul> + * </ul> + * --> + */ + public Bundle getExtras() { + return mExtras; + } + + /** + * Sets the extra information associated with this fix to the + * given Bundle. + */ + public void setExtras(Bundle extras) { + mExtras = (extras == null) ? null : new Bundle(extras); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Address[addressLines=["); + for (int i = 0; i <= mMaxAddressLineIndex; i++) { + if (i > 0) { + sb.append(','); + } + sb.append(i); + sb.append(':'); + String line = mAddressLines.get(i); + if (line == null) { + sb.append("null"); + } else { + sb.append('\"'); + sb.append(line); + sb.append('\"'); + } + } + sb.append(']'); + sb.append(",feature="); + sb.append(mFeatureName); + sb.append(",admin="); + sb.append(mAdminArea); + sb.append(",sub-admin="); + sb.append(mSubAdminArea); + sb.append(",locality="); + sb.append(mLocality); + sb.append(",thoroughfare="); + sb.append(mThoroughfare); + sb.append(",postalCode="); + sb.append(mPostalCode); + sb.append(",countryCode="); + sb.append(mCountryCode); + sb.append(",countryName="); + sb.append(mCountryName); + sb.append(",hasLatitude="); + sb.append(mHasLatitude); + sb.append(",latitude="); + sb.append(mLatitude); + sb.append(",hasLongitude="); + sb.append(mHasLongitude); + sb.append(",longitude="); + sb.append(mLongitude); + sb.append(",phone="); + sb.append(mPhone); + sb.append(",url="); + sb.append(mUrl); + sb.append(",extras="); + sb.append(mExtras); + sb.append(']'); + return sb.toString(); + } + + public static final Parcelable.Creator<Address> CREATOR = + new Parcelable.Creator<Address>() { + public Address createFromParcel(Parcel in) { + String language = in.readString(); + String country = in.readString(); + Locale locale = country.length() > 0 ? + new Locale(language, country) : + new Locale(language); + Address a = new Address(locale); + + int N = in.readInt(); + if (N > 0) { + a.mAddressLines = new HashMap<Integer, String>(N); + for (int i = 0; i < N; i++) { + int index = in.readInt(); + String line = in.readString(); + a.mAddressLines.put(index, line); + a.mMaxAddressLineIndex = + Math.max(a.mMaxAddressLineIndex, index); + } + } else { + a.mAddressLines = null; + a.mMaxAddressLineIndex = -1; + } + a.mFeatureName = in.readString(); + a.mAdminArea = in.readString(); + a.mSubAdminArea = in.readString(); + a.mLocality = in.readString(); + a.mSubLocality = in.readString(); + a.mThoroughfare = in.readString(); + a.mSubThoroughfare = in.readString(); + a.mPremises = in.readString(); + a.mPostalCode = in.readString(); + a.mCountryCode = in.readString(); + a.mCountryName = in.readString(); + a.mHasLatitude = in.readInt() == 0 ? false : true; + if (a.mHasLatitude) { + a.mLatitude = in.readDouble(); + } + a.mHasLongitude = in.readInt() == 0 ? false : true; + if (a.mHasLongitude) { + a.mLongitude = in.readDouble(); + } + a.mPhone = in.readString(); + a.mUrl = in.readString(); + a.mExtras = in.readBundle(); + return a; + } + + public Address[] newArray(int size) { + return new Address[size]; + } + }; + + public int describeContents() { + return (mExtras != null) ? mExtras.describeContents() : 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mLocale.getLanguage()); + parcel.writeString(mLocale.getCountry()); + if (mAddressLines == null) { + parcel.writeInt(0); + } else { + Set<Map.Entry<Integer, String>> entries = mAddressLines.entrySet(); + parcel.writeInt(entries.size()); + for (Map.Entry<Integer, String> e : entries) { + parcel.writeInt(e.getKey()); + parcel.writeString(e.getValue()); + } + } + parcel.writeString(mFeatureName); + parcel.writeString(mAdminArea); + parcel.writeString(mSubAdminArea); + parcel.writeString(mLocality); + parcel.writeString(mSubLocality); + parcel.writeString(mThoroughfare); + parcel.writeString(mSubThoroughfare); + parcel.writeString(mPremises); + parcel.writeString(mPostalCode); + parcel.writeString(mCountryCode); + parcel.writeString(mCountryName); + parcel.writeInt(mHasLatitude ? 1 : 0); + if (mHasLatitude) { + parcel.writeDouble(mLatitude); + } + parcel.writeInt(mHasLongitude ? 1 : 0); + if (mHasLongitude){ + parcel.writeDouble(mLongitude); + } + parcel.writeString(mPhone); + parcel.writeString(mUrl); + parcel.writeBundle(mExtras); + } +} diff --git a/android/location/BatchedLocationCallback.java b/android/location/BatchedLocationCallback.java new file mode 100644 index 00000000..f1c40ae2 --- /dev/null +++ b/android/location/BatchedLocationCallback.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 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.IntDef; +import android.annotation.SystemApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * Used for receiving notifications from the LocationManager when + * the a batch of location is ready. These methods are called if the + * BatchedLocationCallback has been registered with the location manager service + * using the + * {@link LocationManager#registerGnssBatchedLocationCallback#startGnssBatch(long, + * boolean, BatchedLocationCallback, android.os.Handler)} method. + * @hide + */ +@SystemApi +public abstract class BatchedLocationCallback { + + /** + * Called when a new batch of locations is ready + * + * @param locations A list of all new locations (possibly zero of them.) + */ + public void onLocationBatch(List<Location> locations) {} +} diff --git a/android/location/BatchedLocationCallbackTransport.java b/android/location/BatchedLocationCallbackTransport.java new file mode 100644 index 00000000..e00f855e --- /dev/null +++ b/android/location/BatchedLocationCallbackTransport.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.os.RemoteException; + +import java.util.List; + +/** + * A handler class to manage transport callbacks for {@link BatchedLocationCallback}. + * + * @hide + */ +class BatchedLocationCallbackTransport + extends LocalListenerHelper<BatchedLocationCallback> { + private final ILocationManager mLocationManager; + + private final IBatchedLocationCallback mCallbackTransport = new CallbackTransport(); + + public BatchedLocationCallbackTransport(Context context, ILocationManager locationManager) { + super(context, "BatchedLocationCallbackTransport"); + mLocationManager = locationManager; + } + + @Override + protected boolean registerWithServer() throws RemoteException { + return mLocationManager.addGnssBatchingCallback( + mCallbackTransport, + getContext().getPackageName()); + } + + @Override + protected void unregisterFromServer() throws RemoteException { + mLocationManager.removeGnssBatchingCallback(); + } + + private class CallbackTransport extends IBatchedLocationCallback.Stub { + @Override + public void onLocationBatch(final List<Location> locations) { + ListenerOperation<BatchedLocationCallback> operation = + new ListenerOperation<BatchedLocationCallback>() { + @Override + public void execute(BatchedLocationCallback callback) + throws RemoteException { + callback.onLocationBatch(locations); + } + }; + foreach(operation); + } + } +} diff --git a/android/location/Country.java b/android/location/Country.java new file mode 100644 index 00000000..7c1485d8 --- /dev/null +++ b/android/location/Country.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2010 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.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; + +import java.util.Locale; + +/** + * This class wraps the country information. + * + * @hide + */ +public class Country implements Parcelable { + /** + * The country code came from the mobile network + */ + public static final int COUNTRY_SOURCE_NETWORK = 0; + + /** + * The country code came from the location service + */ + public static final int COUNTRY_SOURCE_LOCATION = 1; + + /** + * The country code was read from the SIM card + */ + public static final int COUNTRY_SOURCE_SIM = 2; + + /** + * The country code came from the system locale setting + */ + public static final int COUNTRY_SOURCE_LOCALE = 3; + + /** + * The ISO 3166-1 two letters country code. + */ + private final String mCountryIso; + + /** + * Where the country code came from. + */ + private final int mSource; + + private int mHashCode; + + /** + * Time that this object was created (which we assume to be the time that the source was + * consulted). This time is in milliseconds since boot up. + */ + private final long mTimestamp; + + /** + * @param countryIso the ISO 3166-1 two letters country code. + * @param source where the countryIso came from, could be one of below + * values + * <p> + * <ul> + * <li>{@link #COUNTRY_SOURCE_NETWORK}</li> + * <li>{@link #COUNTRY_SOURCE_LOCATION}</li> + * <li>{@link #COUNTRY_SOURCE_SIM}</li> + * <li>{@link #COUNTRY_SOURCE_LOCALE}</li> + * </ul> + */ + public Country(final String countryIso, final int source) { + if (countryIso == null || source < COUNTRY_SOURCE_NETWORK + || source > COUNTRY_SOURCE_LOCALE) { + throw new IllegalArgumentException(); + } + mCountryIso = countryIso.toUpperCase(Locale.US); + mSource = source; + mTimestamp = SystemClock.elapsedRealtime(); + } + + private Country(final String countryIso, final int source, long timestamp) { + if (countryIso == null || source < COUNTRY_SOURCE_NETWORK + || source > COUNTRY_SOURCE_LOCALE) { + throw new IllegalArgumentException(); + } + mCountryIso = countryIso.toUpperCase(Locale.US); + mSource = source; + mTimestamp = timestamp; + } + + public Country(Country country) { + mCountryIso = country.mCountryIso; + mSource = country.mSource; + mTimestamp = country.mTimestamp; + } + + /** + * @return the ISO 3166-1 two letters country code + */ + public final String getCountryIso() { + return mCountryIso; + } + + /** + * @return where the country code came from, could be one of below values + * <p> + * <ul> + * <li>{@link #COUNTRY_SOURCE_NETWORK}</li> + * <li>{@link #COUNTRY_SOURCE_LOCATION}</li> + * <li>{@link #COUNTRY_SOURCE_SIM}</li> + * <li>{@link #COUNTRY_SOURCE_LOCALE}</li> + * </ul> + */ + public final int getSource() { + return mSource; + } + + /** + * Returns the time that this object was created (which we assume to be the time that the source + * was consulted). + */ + public final long getTimestamp() { + return mTimestamp; + } + + public static final Parcelable.Creator<Country> CREATOR = new Parcelable.Creator<Country>() { + public Country createFromParcel(Parcel in) { + return new Country(in.readString(), in.readInt(), in.readLong()); + } + + public Country[] newArray(int size) { + return new Country[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mCountryIso); + parcel.writeInt(mSource); + parcel.writeLong(mTimestamp); + } + + /** + * Returns true if this {@link Country} is equivalent to the given object. This ignores + * the timestamp value and just checks for equivalence of countryIso and source values. + * Returns false otherwise. + */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof Country) { + Country c = (Country) object; + // No need to check the equivalence of the timestamp + return mCountryIso.equals(c.getCountryIso()) && mSource == c.getSource(); + } + return false; + } + + @Override + public int hashCode() { + int hash = mHashCode; + if (hash == 0) { + hash = 17; + hash = hash * 13 + mCountryIso.hashCode(); + hash = hash * 13 + mSource; + mHashCode = hash; + } + return mHashCode; + } + + /** + * Compare the specified country to this country object ignoring the source + * and timestamp fields, return true if the countryIso fields are equal + * + * @param country the country to compare + * @return true if the specified country's countryIso field is equal to this + * country's, false otherwise. + */ + public boolean equalsIgnoreSource(Country country) { + return country != null && mCountryIso.equals(country.getCountryIso()); + } + + @Override + public String toString() { + return "Country {ISO=" + mCountryIso + ", source=" + mSource + ", time=" + mTimestamp + "}"; + } +} diff --git a/android/location/CountryDetector.java b/android/location/CountryDetector.java new file mode 100644 index 00000000..ec6dfb71 --- /dev/null +++ b/android/location/CountryDetector.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2010 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 java.util.HashMap; + +import android.annotation.SystemService; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; + +/** + * This class provides access to the system country detector service. This + * service allows applications to obtain the country that the user is in. + * <p> + * The country will be detected in order of reliability, like + * <ul> + * <li>Mobile network</li> + * <li>Location</li> + * <li>SIM's country</li> + * <li>Phone's locale</li> + * </ul> + * <p> + * Call the {@link #detectCountry()} to get the available country immediately. + * <p> + * To be notified of the future country change, use the + * {@link #addCountryListener} + * <p> + * + * @hide + */ +@SystemService(Context.COUNTRY_DETECTOR) +public class CountryDetector { + + /** + * The class to wrap the ICountryListener.Stub and CountryListener objects + * together. The CountryListener will be notified through the specific + * looper once the country changed and detected. + */ + private final static class ListenerTransport extends ICountryListener.Stub { + + private final CountryListener mListener; + + private final Handler mHandler; + + public ListenerTransport(CountryListener listener, Looper looper) { + mListener = listener; + if (looper != null) { + mHandler = new Handler(looper); + } else { + mHandler = new Handler(); + } + } + + public void onCountryDetected(final Country country) { + mHandler.post(new Runnable() { + public void run() { + mListener.onCountryDetected(country); + } + }); + } + } + + private final static String TAG = "CountryDetector"; + private final ICountryDetector mService; + private final HashMap<CountryListener, ListenerTransport> mListeners; + + /** + * @hide - hide this constructor because it has a parameter of type + * ICountryDetector, which is a system private class. The right way to + * create an instance of this class is using the factory + * Context.getSystemService. + */ + public CountryDetector(ICountryDetector service) { + mService = service; + mListeners = new HashMap<CountryListener, ListenerTransport>(); + } + + /** + * Start detecting the country that the user is in. + * + * @return the country if it is available immediately, otherwise null will + * be returned. + */ + public Country detectCountry() { + try { + return mService.detectCountry(); + } catch (RemoteException e) { + Log.e(TAG, "detectCountry: RemoteException", e); + return null; + } + } + + /** + * Add a listener to receive the notification when the country is detected + * or changed. + * + * @param listener will be called when the country is detected or changed. + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism. If looper is null then the + * callbacks will be called on the main thread. + */ + public void addCountryListener(CountryListener listener, Looper looper) { + synchronized (mListeners) { + if (!mListeners.containsKey(listener)) { + ListenerTransport transport = new ListenerTransport(listener, looper); + try { + mService.addCountryListener(transport); + mListeners.put(listener, transport); + } catch (RemoteException e) { + Log.e(TAG, "addCountryListener: RemoteException", e); + } + } + } + } + + /** + * Remove the listener + */ + public void removeCountryListener(CountryListener listener) { + synchronized (mListeners) { + ListenerTransport transport = mListeners.get(listener); + if (transport != null) { + try { + mListeners.remove(listener); + mService.removeCountryListener(transport); + } catch (RemoteException e) { + Log.e(TAG, "removeCountryListener: RemoteException", e); + } + } + } + } +} diff --git a/android/location/CountryListener.java b/android/location/CountryListener.java new file mode 100644 index 00000000..e36db412 --- /dev/null +++ b/android/location/CountryListener.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2010 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; + +/** + * The listener for receiving the notification when the country is detected or + * changed + * + * @hide + */ +public interface CountryListener { + /** + * @param country the changed or detected country. + */ + void onCountryDetected(Country country); +} diff --git a/android/location/Criteria.java b/android/location/Criteria.java new file mode 100644 index 00000000..a6099be6 --- /dev/null +++ b/android/location/Criteria.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2007 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.os.Parcel; +import android.os.Parcelable; + +/** + * A class indicating the application criteria for selecting a + * location provider. Providers maybe ordered according to accuracy, + * power usage, ability to report altitude, speed, + * and bearing, and monetary cost. + */ +public class Criteria implements Parcelable { + /** + * A constant indicating that the application does not choose to + * place requirement on a particular feature. + */ + public static final int NO_REQUIREMENT = 0; + + /** + * A constant indicating a low power requirement. + */ + public static final int POWER_LOW = 1; + + /** + * A constant indicating a medium power requirement. + */ + public static final int POWER_MEDIUM = 2; + + /** + * A constant indicating a high power requirement. + */ + public static final int POWER_HIGH = 3; + + /** + * A constant indicating a finer location accuracy requirement + */ + public static final int ACCURACY_FINE = 1; + + /** + * A constant indicating an approximate accuracy requirement + */ + public static final int ACCURACY_COARSE = 2; + + /** + * A constant indicating a low location accuracy requirement + * - may be used for horizontal, altitude, speed or bearing accuracy. + * For horizontal and vertical position this corresponds roughly to + * an accuracy of greater than 500 meters. + */ + public static final int ACCURACY_LOW = 1; + + /** + * A constant indicating a medium accuracy requirement + * - currently used only for horizontal accuracy. + * For horizontal position this corresponds roughly to to an accuracy + * of between 100 and 500 meters. + */ + public static final int ACCURACY_MEDIUM = 2; + + /** + * a constant indicating a high accuracy requirement + * - may be used for horizontal, altitude, speed or bearing accuracy. + * For horizontal and vertical position this corresponds roughly to + * an accuracy of less than 100 meters. + */ + public static final int ACCURACY_HIGH = 3; + + private int mHorizontalAccuracy = NO_REQUIREMENT; + private int mVerticalAccuracy = NO_REQUIREMENT; + private int mSpeedAccuracy = NO_REQUIREMENT; + private int mBearingAccuracy = NO_REQUIREMENT; + private int mPowerRequirement = NO_REQUIREMENT; + private boolean mAltitudeRequired = false; + private boolean mBearingRequired = false; + private boolean mSpeedRequired = false; + private boolean mCostAllowed = false; + + /** + * Constructs a new Criteria object. The new object will have no + * requirements on accuracy, power, or response time; will not + * require altitude, speed, or bearing; and will not allow monetary + * cost. + */ + public Criteria() {} + + /** + * Constructs a new Criteria object that is a copy of the given criteria. + */ + public Criteria(Criteria criteria) { + mHorizontalAccuracy = criteria.mHorizontalAccuracy; + mVerticalAccuracy = criteria.mVerticalAccuracy; + mSpeedAccuracy = criteria.mSpeedAccuracy; + mBearingAccuracy = criteria.mBearingAccuracy; + mPowerRequirement = criteria.mPowerRequirement; + mAltitudeRequired = criteria.mAltitudeRequired; + mBearingRequired = criteria.mBearingRequired; + mSpeedRequired = criteria.mSpeedRequired; + mCostAllowed = criteria.mCostAllowed; + } + + /** + * Indicates the desired horizontal accuracy (latitude and longitude). + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + */ + public void setHorizontalAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mHorizontalAccuracy = accuracy; + } + + /** + * Returns a constant indicating the desired horizontal accuracy (latitude and longitude). + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. + */ + public int getHorizontalAccuracy() { + return mHorizontalAccuracy; + } + + /** + * Indicates the desired vertical accuracy (altitude). + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + */ + public void setVerticalAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mVerticalAccuracy = accuracy; + } + + /** + * Returns a constant indicating the desired vertical accuracy (altitude). + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, + * or {@link #NO_REQUIREMENT}. + */ + public int getVerticalAccuracy() { + return mVerticalAccuracy; + } + + /** + * Indicates the desired speed accuracy. + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, + * or {@link #NO_REQUIREMENT}. + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + */ + public void setSpeedAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mSpeedAccuracy = accuracy; + } + + /** + * Returns a constant indicating the desired speed accuracy + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, + * or {@link #NO_REQUIREMENT}. + */ + public int getSpeedAccuracy() { + return mSpeedAccuracy; + } + + /** + * Indicates the desired bearing accuracy. + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, + * or {@link #NO_REQUIREMENT}. + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + */ + public void setBearingAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mBearingAccuracy = accuracy; + } + + /** + * Returns a constant indicating the desired bearing accuracy. + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, + * or {@link #NO_REQUIREMENT}. + */ + public int getBearingAccuracy() { + return mBearingAccuracy; + } + + /** + * Indicates the desired accuracy for latitude and longitude. Accuracy + * may be {@link #ACCURACY_FINE} if desired location + * is fine, else it can be {@link #ACCURACY_COARSE}. + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + */ + public void setAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + if (accuracy == ACCURACY_FINE) { + mHorizontalAccuracy = ACCURACY_HIGH; + } else { + mHorizontalAccuracy = ACCURACY_LOW; + } + } + + /** + * Returns a constant indicating desired accuracy of location + * Accuracy may be {@link #ACCURACY_FINE} if desired location + * is fine, else it can be {@link #ACCURACY_COARSE}. + */ + public int getAccuracy() { + if (mHorizontalAccuracy >= ACCURACY_HIGH) { + return ACCURACY_FINE; + } else { + return ACCURACY_COARSE; + } + } + + /** + * Indicates the desired maximum power level. The level parameter + * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or + * POWER_HIGH. + */ + public void setPowerRequirement(int level) { + if (level < NO_REQUIREMENT || level > POWER_HIGH) { + throw new IllegalArgumentException("level=" + level); + } + mPowerRequirement = level; + } + + /** + * Returns a constant indicating the desired power requirement. The + * returned + */ + public int getPowerRequirement() { + return mPowerRequirement; + } + + /** + * Indicates whether the provider is allowed to incur monetary cost. + */ + public void setCostAllowed(boolean costAllowed) { + mCostAllowed = costAllowed; + } + + /** + * Returns whether the provider is allowed to incur monetary cost. + */ + public boolean isCostAllowed() { + return mCostAllowed; + } + + /** + * Indicates whether the provider must provide altitude information. + * Not all fixes are guaranteed to contain such information. + */ + public void setAltitudeRequired(boolean altitudeRequired) { + mAltitudeRequired = altitudeRequired; + } + + /** + * Returns whether the provider must provide altitude information. + * Not all fixes are guaranteed to contain such information. + */ + public boolean isAltitudeRequired() { + return mAltitudeRequired; + } + + /** + * Indicates whether the provider must provide speed information. + * Not all fixes are guaranteed to contain such information. + */ + public void setSpeedRequired(boolean speedRequired) { + mSpeedRequired = speedRequired; + } + + /** + * Returns whether the provider must provide speed information. + * Not all fixes are guaranteed to contain such information. + */ + public boolean isSpeedRequired() { + return mSpeedRequired; + } + + /** + * Indicates whether the provider must provide bearing information. + * Not all fixes are guaranteed to contain such information. + */ + public void setBearingRequired(boolean bearingRequired) { + mBearingRequired = bearingRequired; + } + + /** + * Returns whether the provider must provide bearing information. + * Not all fixes are guaranteed to contain such information. + */ + public boolean isBearingRequired() { + return mBearingRequired; + } + + public static final Parcelable.Creator<Criteria> CREATOR = + new Parcelable.Creator<Criteria>() { + @Override + public Criteria createFromParcel(Parcel in) { + Criteria c = new Criteria(); + c.mHorizontalAccuracy = in.readInt(); + c.mVerticalAccuracy = in.readInt(); + c.mSpeedAccuracy = in.readInt(); + c.mBearingAccuracy = in.readInt(); + c.mPowerRequirement = in.readInt(); + c.mAltitudeRequired = in.readInt() != 0; + c.mBearingRequired = in.readInt() != 0; + c.mSpeedRequired = in.readInt() != 0; + c.mCostAllowed = in.readInt() != 0; + return c; + } + + @Override + public Criteria[] newArray(int size) { + return new Criteria[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mHorizontalAccuracy); + parcel.writeInt(mVerticalAccuracy); + parcel.writeInt(mSpeedAccuracy); + parcel.writeInt(mBearingAccuracy); + parcel.writeInt(mPowerRequirement); + parcel.writeInt(mAltitudeRequired ? 1 : 0); + parcel.writeInt(mBearingRequired ? 1 : 0); + parcel.writeInt(mSpeedRequired ? 1 : 0); + parcel.writeInt(mCostAllowed ? 1 : 0); + } + + private static String powerToString(int power) { + switch (power) { + case NO_REQUIREMENT: + return "NO_REQ"; + case POWER_LOW: + return "LOW"; + case POWER_MEDIUM: + return "MEDIUM"; + case POWER_HIGH: + return "HIGH"; + default: + return "???"; + } + } + + private static String accuracyToString(int accuracy) { + switch (accuracy) { + case NO_REQUIREMENT: + return "---"; + case ACCURACY_HIGH: + return "HIGH"; + case ACCURACY_MEDIUM: + return "MEDIUM"; + case ACCURACY_LOW: + return "LOW"; + default: + return "???"; + } + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Criteria[power=").append(powerToString(mPowerRequirement)); + s.append(" acc=").append(accuracyToString(mHorizontalAccuracy)); + s.append(']'); + return s.toString(); + } +} diff --git a/android/location/FusedBatchOptions.java b/android/location/FusedBatchOptions.java new file mode 100644 index 00000000..aa4a8602 --- /dev/null +++ b/android/location/FusedBatchOptions.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013 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.os.Parcel; +import android.os.Parcelable; + +/** + * A data class representing a set of options to configure batching sessions. + * @hide + */ +public class FusedBatchOptions implements Parcelable { + private volatile long mPeriodInNS = 0; + private volatile int mSourcesToUse = 0; + private volatile int mFlags = 0; + + // the default value is set to request fixes at no cost + private volatile double mMaxPowerAllocationInMW = 0; + // If non-zero can be used for power savings by throttling location when device hasn't moved. + private volatile float mSmallestDisplacementMeters = 0; + + /* + * Getters and setters for properties needed to hold the options. + */ + public void setMaxPowerAllocationInMW(double value) { + mMaxPowerAllocationInMW = value; + } + + public double getMaxPowerAllocationInMW() { + return mMaxPowerAllocationInMW; + } + + public void setPeriodInNS(long value) { + mPeriodInNS = value; + } + + public long getPeriodInNS() { + return mPeriodInNS; + } + + public void setSmallestDisplacementMeters(float value) { + mSmallestDisplacementMeters = value; + } + + public float getSmallestDisplacementMeters() { + return mSmallestDisplacementMeters; + } + + public void setSourceToUse(int source) { + mSourcesToUse |= source; + } + + public void resetSourceToUse(int source) { + mSourcesToUse &= ~source; + } + + public boolean isSourceToUseSet(int source) { + return (mSourcesToUse & source) != 0; + } + + public int getSourcesToUse() { + return mSourcesToUse; + } + + public void setFlag(int flag) { + mFlags |= flag; + } + + public void resetFlag(int flag) { + mFlags &= ~flag; + } + + public boolean isFlagSet(int flag) { + return (mFlags & flag) != 0; + } + + public int getFlags() { + return mFlags; + } + + /** + * Definition of enum flag sets needed by this class. + * Such values need to be kept in sync with the ones in fused_location.h + */ + public static final class SourceTechnologies { + public static int GNSS = 1<<0; + public static int WIFI = 1<<1; + public static int SENSORS = 1<<2; + public static int CELL = 1<<3; + public static int BLUETOOTH = 1<<4; + } + + public static final class BatchFlags { + // follow the definitions to the letter in fused_location.h + public static int WAKEUP_ON_FIFO_FULL = 0x0000001; + public static int CALLBACK_ON_LOCATION_FIX =0x0000002; + } + + /* + * Method definitions to support Parcelable operations. + */ + public static final Parcelable.Creator<FusedBatchOptions> CREATOR = + new Parcelable.Creator<FusedBatchOptions>() { + @Override + public FusedBatchOptions createFromParcel(Parcel parcel) { + FusedBatchOptions options = new FusedBatchOptions(); + options.setMaxPowerAllocationInMW(parcel.readDouble()); + options.setPeriodInNS(parcel.readLong()); + options.setSourceToUse(parcel.readInt()); + options.setFlag(parcel.readInt()); + options.setSmallestDisplacementMeters(parcel.readFloat()); + return options; + } + + @Override + public FusedBatchOptions[] newArray(int size) { + return new FusedBatchOptions[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeDouble(mMaxPowerAllocationInMW); + parcel.writeLong(mPeriodInNS); + parcel.writeInt(mSourcesToUse); + parcel.writeInt(mFlags); + parcel.writeFloat(mSmallestDisplacementMeters); + } +} diff --git a/android/location/Geocoder.java b/android/location/Geocoder.java new file mode 100644 index 00000000..ac7eb8b1 --- /dev/null +++ b/android/location/Geocoder.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2007 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.content.Context; +import android.location.Address; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + +import java.io.IOException; +import java.util.Locale; +import java.util.ArrayList; +import java.util.List; + +/** + * A class for handling geocoding and reverse geocoding. Geocoding is + * the process of transforming a street address or other description + * of a location into a (latitude, longitude) coordinate. Reverse + * geocoding is the process of transforming a (latitude, longitude) + * coordinate into a (partial) address. The amount of detail in a + * reverse geocoded location description may vary, for example one + * might contain the full street address of the closest building, while + * another might contain only a city name and postal code. + * + * The Geocoder class requires a backend service that is not included in + * the core android framework. The Geocoder query methods will return an + * empty list if there no backend service in the platform. Use the + * isPresent() method to determine whether a Geocoder implementation + * exists. + */ +public final class Geocoder { + private static final String TAG = "Geocoder"; + + private GeocoderParams mParams; + private ILocationManager mService; + + /** + * Returns true if the Geocoder methods getFromLocation and + * getFromLocationName are implemented. Lack of network + * connectivity may still cause these methods to return null or + * empty lists. + */ + public static boolean isPresent() { + IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE); + ILocationManager lm = ILocationManager.Stub.asInterface(b); + try { + return lm.geocoderIsPresent(); + } catch (RemoteException e) { + Log.e(TAG, "isPresent: got RemoteException", e); + return false; + } + } + + /** + * Constructs a Geocoder whose responses will be localized for the + * given Locale. + * + * @param context the Context of the calling Activity + * @param locale the desired Locale for the query results + * + * @throws NullPointerException if Locale is null + */ + public Geocoder(Context context, Locale locale) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + mParams = new GeocoderParams(context, locale); + IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE); + mService = ILocationManager.Stub.asInterface(b); + } + + /** + * Constructs a Geocoder whose responses will be localized for the + * default system Locale. + * + * @param context the Context of the calling Activity + */ + public Geocoder(Context context) { + this(context, Locale.getDefault()); + } + + /** + * Returns an array of Addresses that are known to describe the + * area immediately surrounding the given latitude and longitude. + * The returned addresses will be localized for the locale + * provided to this class's constructor. + * + * <p> The returned values may be obtained by means of a network lookup. + * The results are a best guess and are not guaranteed to be meaningful or + * correct. It may be useful to call this method from a thread separate from your + * primary UI thread. + * + * @param latitude the latitude a point for the search + * @param longitude the longitude a point for the search + * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended + * + * @return a list of Address objects. Returns null or empty list if no matches were + * found or there is no backend service available. + * + * @throws IllegalArgumentException if latitude is + * less than -90 or greater than 90 + * @throws IllegalArgumentException if longitude is + * less than -180 or greater than 180 + * @throws IOException if the network is unavailable or any other + * I/O problem occurs + */ + public List<Address> getFromLocation(double latitude, double longitude, int maxResults) + throws IOException { + if (latitude < -90.0 || latitude > 90.0) { + throw new IllegalArgumentException("latitude == " + latitude); + } + if (longitude < -180.0 || longitude > 180.0) { + throw new IllegalArgumentException("longitude == " + longitude); + } + try { + List<Address> results = new ArrayList<Address>(); + String ex = mService.getFromLocation(latitude, longitude, maxResults, + mParams, results); + if (ex != null) { + throw new IOException(ex); + } else { + return results; + } + } catch (RemoteException e) { + Log.e(TAG, "getFromLocation: got RemoteException", e); + return null; + } + } + + /** + * Returns an array of Addresses that are known to describe the + * named location, which may be a place name such as "Dalvik, + * Iceland", an address such as "1600 Amphitheatre Parkway, + * Mountain View, CA", an airport code such as "SFO", etc.. The + * returned addresses will be localized for the locale provided to + * this class's constructor. + * + * <p> The query will block and returned values will be obtained by means of a network lookup. + * The results are a best guess and are not guaranteed to be meaningful or + * correct. It may be useful to call this method from a thread separate from your + * primary UI thread. + * + * @param locationName a user-supplied description of a location + * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended + * + * @return a list of Address objects. Returns null or empty list if no matches were + * found or there is no backend service available. + * + * @throws IllegalArgumentException if locationName is null + * @throws IOException if the network is unavailable or any other + * I/O problem occurs + */ + public List<Address> getFromLocationName(String locationName, int maxResults) throws IOException { + if (locationName == null) { + throw new IllegalArgumentException("locationName == null"); + } + try { + List<Address> results = new ArrayList<Address>(); + String ex = mService.getFromLocationName(locationName, + 0, 0, 0, 0, maxResults, mParams, results); + if (ex != null) { + throw new IOException(ex); + } else { + return results; + } + } catch (RemoteException e) { + Log.e(TAG, "getFromLocationName: got RemoteException", e); + return null; + } + } + + /** + * Returns an array of Addresses that are known to describe the + * named location, which may be a place name such as "Dalvik, + * Iceland", an address such as "1600 Amphitheatre Parkway, + * Mountain View, CA", an airport code such as "SFO", etc.. The + * returned addresses will be localized for the locale provided to + * this class's constructor. + * + * <p> You may specify a bounding box for the search results by including + * the Latitude and Longitude of the Lower Left point and Upper Right + * point of the box. + * + * <p> The query will block and returned values will be obtained by means of a network lookup. + * The results are a best guess and are not guaranteed to be meaningful or + * correct. It may be useful to call this method from a thread separate from your + * primary UI thread. + * + * @param locationName a user-supplied description of a location + * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended + * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box + * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box + * @param upperRightLatitude the latitude of the upper right corner of the bounding box + * @param upperRightLongitude the longitude of the upper right corner of the bounding box + * + * @return a list of Address objects. Returns null or empty list if no matches were + * found or there is no backend service available. + * + * @throws IllegalArgumentException if locationName is null + * @throws IllegalArgumentException if any latitude is + * less than -90 or greater than 90 + * @throws IllegalArgumentException if any longitude is + * less than -180 or greater than 180 + * @throws IOException if the network is unavailable or any other + * I/O problem occurs + */ + public List<Address> getFromLocationName(String locationName, int maxResults, + double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude) throws IOException { + if (locationName == null) { + throw new IllegalArgumentException("locationName == null"); + } + if (lowerLeftLatitude < -90.0 || lowerLeftLatitude > 90.0) { + throw new IllegalArgumentException("lowerLeftLatitude == " + + lowerLeftLatitude); + } + if (lowerLeftLongitude < -180.0 || lowerLeftLongitude > 180.0) { + throw new IllegalArgumentException("lowerLeftLongitude == " + + lowerLeftLongitude); + } + if (upperRightLatitude < -90.0 || upperRightLatitude > 90.0) { + throw new IllegalArgumentException("upperRightLatitude == " + + upperRightLatitude); + } + if (upperRightLongitude < -180.0 || upperRightLongitude > 180.0) { + throw new IllegalArgumentException("upperRightLongitude == " + + upperRightLongitude); + } + try { + ArrayList<Address> result = new ArrayList<Address>(); + String ex = mService.getFromLocationName(locationName, + lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, + maxResults, mParams, result); + if (ex != null) { + throw new IOException(ex); + } else { + return result; + } + } catch (RemoteException e) { + Log.e(TAG, "getFromLocationName: got RemoteException", e); + return null; + } + } +} diff --git a/android/location/GeocoderParams.java b/android/location/GeocoderParams.java new file mode 100644 index 00000000..174fe3e0 --- /dev/null +++ b/android/location/GeocoderParams.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 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.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Locale; + +/** + * This class contains extra parameters to pass to an IGeocodeProvider + * implementation from the Geocoder class. Currently this contains the + * language, country and variant information from the Geocoder's locale + * as well as the Geocoder client's package name for geocoder server + * logging. This information is kept in a separate class to allow for + * future expansion of the IGeocodeProvider interface. + * + * @hide + */ +public class GeocoderParams implements Parcelable { + private Locale mLocale; + private String mPackageName; + + // used only for parcelling + private GeocoderParams() { + } + + /** + * This object is only constructed by the Geocoder class + * + * @hide + */ + public GeocoderParams(Context context, Locale locale) { + mLocale = locale; + mPackageName = context.getPackageName(); + } + + /** + * returns the Geocoder's locale + */ + public Locale getLocale() { + return mLocale; + } + + /** + * returns the package name of the Geocoder's client + */ + public String getClientPackage() { + return mPackageName; + } + + public static final Parcelable.Creator<GeocoderParams> CREATOR = + new Parcelable.Creator<GeocoderParams>() { + public GeocoderParams createFromParcel(Parcel in) { + GeocoderParams gp = new GeocoderParams(); + String language = in.readString(); + String country = in.readString(); + String variant = in.readString(); + gp.mLocale = new Locale(language, country, variant); + gp.mPackageName = in.readString(); + return gp; + } + + public GeocoderParams[] newArray(int size) { + return new GeocoderParams[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mLocale.getLanguage()); + parcel.writeString(mLocale.getCountry()); + parcel.writeString(mLocale.getVariant()); + parcel.writeString(mPackageName); + } +} diff --git a/android/location/Geofence.java b/android/location/Geofence.java new file mode 100644 index 00000000..5de779a2 --- /dev/null +++ b/android/location/Geofence.java @@ -0,0 +1,182 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a geographical boundary, also known as a geofence. + * + * <p>Currently only circular geofences are supported and they do not support altitude changes. + * + * @hide + */ +public final class Geofence implements Parcelable { + /** @hide */ + public static final int TYPE_HORIZONTAL_CIRCLE = 1; + + private final int mType; + private final double mLatitude; + private final double mLongitude; + private final float mRadius; + + /** + * Create a circular geofence (on a flat, horizontal plane). + * + * @param latitude latitude in degrees, between -90 and +90 inclusive + * @param longitude longitude in degrees, between -180 and +180 inclusive + * @param radius radius in meters + * @return a new geofence + * @throws IllegalArgumentException if any parameters are out of range + */ + public static Geofence createCircle(double latitude, double longitude, float radius) { + return new Geofence(latitude, longitude, radius); + } + + private Geofence(double latitude, double longitude, float radius) { + checkRadius(radius); + checkLatLong(latitude, longitude); + mType = TYPE_HORIZONTAL_CIRCLE; + mLatitude = latitude; + mLongitude = longitude; + mRadius = radius; + } + + /** @hide */ + public int getType() { + return mType; + } + + /** @hide */ + public double getLatitude() { + return mLatitude; + } + + /** @hide */ + public double getLongitude() { + return mLongitude; + } + + /** @hide */ + public float getRadius() { + return mRadius; + } + + private static void checkRadius(float radius) { + if (radius <= 0) { + throw new IllegalArgumentException("invalid radius: " + radius); + } + } + + private static void checkLatLong(double latitude, double longitude) { + if (latitude > 90.0 || latitude < -90.0) { + throw new IllegalArgumentException("invalid latitude: " + latitude); + } + if (longitude > 180.0 || longitude < -180.0) { + throw new IllegalArgumentException("invalid longitude: " + longitude); + } + } + + private static void checkType(int type) { + if (type != TYPE_HORIZONTAL_CIRCLE) { + throw new IllegalArgumentException("invalid type: " + type); + } + } + + public static final Parcelable.Creator<Geofence> CREATOR = new Parcelable.Creator<Geofence>() { + @Override + public Geofence createFromParcel(Parcel in) { + int type = in.readInt(); + double latitude = in.readDouble(); + double longitude = in.readDouble(); + float radius = in.readFloat(); + checkType(type); + return Geofence.createCircle(latitude, longitude, radius); + } + @Override + public Geofence[] newArray(int size) { + return new Geofence[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mType); + parcel.writeDouble(mLatitude); + parcel.writeDouble(mLongitude); + parcel.writeFloat(mRadius); + } + + private static String typeToString(int type) { + switch (type) { + case TYPE_HORIZONTAL_CIRCLE: + return "CIRCLE"; + default: + checkType(type); + return null; + } + } + + @Override + public String toString() { + return String.format("Geofence[%s %.6f, %.6f %.0fm]", + typeToString(mType), mLatitude, mLongitude, mRadius); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(mLatitude); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(mLongitude); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + Float.floatToIntBits(mRadius); + result = prime * result + mType; + return result; + } + + /** + * Two geofences are equal if they have identical properties. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Geofence)) + return false; + Geofence other = (Geofence) obj; + if (mRadius != other.mRadius) + return false; + if (mLatitude != other.mLatitude) + return false; + if (mLongitude != other.mLongitude) + return false; + if (mType != other.mType) + return false; + return true; + } +} diff --git a/android/location/GnssClock.java b/android/location/GnssClock.java new file mode 100644 index 00000000..25d247de --- /dev/null +++ b/android/location/GnssClock.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2014 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.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class containing a GPS clock timestamp. + * + * <p>It represents a measurement of the GPS receiver's clock. + */ +public final class GnssClock implements Parcelable { + // The following enumerations must be in sync with the values declared in gps.h + + private static final int HAS_NO_FLAGS = 0; + private static final int HAS_LEAP_SECOND = (1<<0); + private static final int HAS_TIME_UNCERTAINTY = (1<<1); + private static final int HAS_FULL_BIAS = (1<<2); + private static final int HAS_BIAS = (1<<3); + private static final int HAS_BIAS_UNCERTAINTY = (1<<4); + private static final int HAS_DRIFT = (1<<5); + private static final int HAS_DRIFT_UNCERTAINTY = (1<<6); + + // End enumerations in sync with gps.h + + private int mFlags; + private int mLeapSecond; + private long mTimeNanos; + private double mTimeUncertaintyNanos; + private long mFullBiasNanos; + private double mBiasNanos; + private double mBiasUncertaintyNanos; + private double mDriftNanosPerSecond; + private double mDriftUncertaintyNanosPerSecond; + private int mHardwareClockDiscontinuityCount; + + /** + * @hide + */ + @TestApi + public GnssClock() { + initialize(); + } + + /** + * Sets all contents to the values stored in the provided object. + * @hide + */ + @TestApi + public void set(GnssClock clock) { + mFlags = clock.mFlags; + mLeapSecond = clock.mLeapSecond; + mTimeNanos = clock.mTimeNanos; + mTimeUncertaintyNanos = clock.mTimeUncertaintyNanos; + mFullBiasNanos = clock.mFullBiasNanos; + mBiasNanos = clock.mBiasNanos; + mBiasUncertaintyNanos = clock.mBiasUncertaintyNanos; + mDriftNanosPerSecond = clock.mDriftNanosPerSecond; + mDriftUncertaintyNanosPerSecond = clock.mDriftUncertaintyNanosPerSecond; + mHardwareClockDiscontinuityCount = clock.mHardwareClockDiscontinuityCount; + } + + /** + * Resets all the contents to its original state. + * @hide + */ + @TestApi + public void reset() { + initialize(); + } + + /** + * Returns {@code true} if {@link #getLeapSecond()} is available, {@code false} otherwise. + */ + public boolean hasLeapSecond() { + return isFlagSet(HAS_LEAP_SECOND); + } + + /** + * Gets the leap second associated with the clock's time. + * + * <p>The sign of the value is defined by the following equation: + * <pre> + * UtcTimeNanos = TimeNanos - (FullBiasNanos + BiasNanos) - LeapSecond * 1,000,000,000</pre> + * + * <p>The value is only available if {@link #hasLeapSecond()} is {@code true}. + */ + public int getLeapSecond() { + return mLeapSecond; + } + + /** + * Sets the leap second associated with the clock's time. + * @hide + */ + @TestApi + public void setLeapSecond(int leapSecond) { + setFlag(HAS_LEAP_SECOND); + mLeapSecond = leapSecond; + } + + /** + * Resets the leap second associated with the clock's time. + * @hide + */ + @TestApi + public void resetLeapSecond() { + resetFlag(HAS_LEAP_SECOND); + mLeapSecond = Integer.MIN_VALUE; + } + + /** + * Gets the GNSS receiver internal hardware clock value in nanoseconds. + * + * <p>This value is expected to be monotonically increasing while the hardware clock remains + * powered on. For the case of a hardware clock that is not continuously on, see the + * {@link #getHardwareClockDiscontinuityCount} field. The GPS time can be derived by subtracting + * the sum of {@link #getFullBiasNanos()} and {@link #getBiasNanos()} (when they are available) + * from this value. Sub-nanosecond accuracy can be provided by means of {@link #getBiasNanos()}. + * + * <p>The error estimate for this value (if applicable) is {@link #getTimeUncertaintyNanos()}. + */ + public long getTimeNanos() { + return mTimeNanos; + } + + /** + * Sets the GNSS receiver internal clock in nanoseconds. + * @hide + */ + @TestApi + public void setTimeNanos(long timeNanos) { + mTimeNanos = timeNanos; + } + + /** + * Returns {@code true} if {@link #getTimeUncertaintyNanos()} is available, {@code false} + * otherwise. + */ + public boolean hasTimeUncertaintyNanos() { + return isFlagSet(HAS_TIME_UNCERTAINTY); + } + + /** + * Gets the clock's time Uncertainty (1-Sigma) in nanoseconds. + * + * <p>The uncertainty is represented as an absolute (single sided) value. + * + * <p>The value is only available if {@link #hasTimeUncertaintyNanos()} is {@code true}. + * + * <p>This value is often effectively zero (it is the reference clock by which all other times + * and time uncertainties are measured), and thus this field may often be 0, or not provided. + */ + public double getTimeUncertaintyNanos() { + return mTimeUncertaintyNanos; + } + + /** + * Sets the clock's Time Uncertainty (1-Sigma) in nanoseconds. + * @hide + */ + @TestApi + public void setTimeUncertaintyNanos(double timeUncertaintyNanos) { + setFlag(HAS_TIME_UNCERTAINTY); + mTimeUncertaintyNanos = timeUncertaintyNanos; + } + + /** + * Resets the clock's Time Uncertainty (1-Sigma) in nanoseconds. + * @hide + */ + @TestApi + public void resetTimeUncertaintyNanos() { + resetFlag(HAS_TIME_UNCERTAINTY); + mTimeUncertaintyNanos = Double.NaN; + } + + /** + * Returns {@code true} if {@link #getFullBiasNanos()} is available, {@code false} otherwise. + */ + public boolean hasFullBiasNanos() { + return isFlagSet(HAS_FULL_BIAS); + } + + /** + * Gets the difference between hardware clock ({@link #getTimeNanos()}) inside GPS receiver and + * the true GPS time since 0000Z, January 6, 1980, in nanoseconds. + * + * <p>This value is available if the receiver has estimated GPS time. If the computed time is + * for a non-GPS constellation, the time offset of that constellation to GPS has to be applied + * to fill this value. The value is only available if {@link #hasFullBiasNanos()} is + * {@code true}. + * + * <p>The error estimate for the sum of this field and {@link #getBiasNanos} is + * {@link #getBiasUncertaintyNanos()}. + * + * <p>The sign of the value is defined by the following equation: + * + * <pre> + * local estimate of GPS time = TimeNanos - (FullBiasNanos + BiasNanos)</pre> + */ + public long getFullBiasNanos() { + return mFullBiasNanos; + } + + /** + * Sets the full bias in nanoseconds. + * @hide + */ + @TestApi + public void setFullBiasNanos(long value) { + setFlag(HAS_FULL_BIAS); + mFullBiasNanos = value; + } + + /** + * Resets the full bias in nanoseconds. + * @hide + */ + @TestApi + public void resetFullBiasNanos() { + resetFlag(HAS_FULL_BIAS); + mFullBiasNanos = Long.MIN_VALUE; + } + + /** + * Returns {@code true} if {@link #getBiasNanos()} is available, {@code false} otherwise. + */ + public boolean hasBiasNanos() { + return isFlagSet(HAS_BIAS); + } + + /** + * Gets the clock's sub-nanosecond bias. + * + * <p>See the description of how this field is part of converting from hardware clock time, to + * GPS time, in {@link #getFullBiasNanos()}. + * + * <p>The error estimate for the sum of this field and {@link #getFullBiasNanos} is + * {@link #getBiasUncertaintyNanos()}. + * + * <p>The value is only available if {@link #hasBiasNanos()} is {@code true}. + */ + public double getBiasNanos() { + return mBiasNanos; + } + + /** + * Sets the sub-nanosecond bias. + * @hide + */ + @TestApi + public void setBiasNanos(double biasNanos) { + setFlag(HAS_BIAS); + mBiasNanos = biasNanos; + } + + /** + * Resets the clock's Bias in nanoseconds. + * @hide + */ + @TestApi + public void resetBiasNanos() { + resetFlag(HAS_BIAS); + mBiasNanos = Double.NaN; + } + + /** + * Returns {@code true} if {@link #getBiasUncertaintyNanos()} is available, {@code false} + * otherwise. + */ + public boolean hasBiasUncertaintyNanos() { + return isFlagSet(HAS_BIAS_UNCERTAINTY); + } + + /** + * Gets the clock's Bias Uncertainty (1-Sigma) in nanoseconds. + * + * <p>See the description of how this field provides the error estimate in the conversion from + * hardware clock time, to GPS time, in {@link #getFullBiasNanos()}. + * + * <p>The value is only available if {@link #hasBiasUncertaintyNanos()} is {@code true}. + */ + public double getBiasUncertaintyNanos() { + return mBiasUncertaintyNanos; + } + + /** + * Sets the clock's Bias Uncertainty (1-Sigma) in nanoseconds. + * @hide + */ + @TestApi + public void setBiasUncertaintyNanos(double biasUncertaintyNanos) { + setFlag(HAS_BIAS_UNCERTAINTY); + mBiasUncertaintyNanos = biasUncertaintyNanos; + } + + /** + * Resets the clock's Bias Uncertainty (1-Sigma) in nanoseconds. + * @hide + */ + @TestApi + public void resetBiasUncertaintyNanos() { + resetFlag(HAS_BIAS_UNCERTAINTY); + mBiasUncertaintyNanos = Double.NaN; + } + + /** + * Returns {@code true} if {@link #getDriftNanosPerSecond()} is available, {@code false} + * otherwise. + */ + public boolean hasDriftNanosPerSecond() { + return isFlagSet(HAS_DRIFT); + } + + /** + * Gets the clock's Drift in nanoseconds per second. + * + * <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master + * clock) frequency. The error estimate for this reported drift is + * {@link #getDriftUncertaintyNanosPerSecond()}. + * + * <p>The value is only available if {@link #hasDriftNanosPerSecond()} is {@code true}. + */ + public double getDriftNanosPerSecond() { + return mDriftNanosPerSecond; + } + + /** + * Sets the clock's Drift in nanoseconds per second. + * @hide + */ + @TestApi + public void setDriftNanosPerSecond(double driftNanosPerSecond) { + setFlag(HAS_DRIFT); + mDriftNanosPerSecond = driftNanosPerSecond; + } + + /** + * Resets the clock's Drift in nanoseconds per second. + * @hide + */ + @TestApi + public void resetDriftNanosPerSecond() { + resetFlag(HAS_DRIFT); + mDriftNanosPerSecond = Double.NaN; + } + + /** + * Returns {@code true} if {@link #getDriftUncertaintyNanosPerSecond()} is available, + * {@code false} otherwise. + */ + public boolean hasDriftUncertaintyNanosPerSecond() { + return isFlagSet(HAS_DRIFT_UNCERTAINTY); + } + + /** + * Gets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second. + * + * <p>The value is only available if {@link #hasDriftUncertaintyNanosPerSecond()} is + * {@code true}. + */ + public double getDriftUncertaintyNanosPerSecond() { + return mDriftUncertaintyNanosPerSecond; + } + + /** + * Sets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second. + * @hide + */ + @TestApi + public void setDriftUncertaintyNanosPerSecond(double driftUncertaintyNanosPerSecond) { + setFlag(HAS_DRIFT_UNCERTAINTY); + mDriftUncertaintyNanosPerSecond = driftUncertaintyNanosPerSecond; + } + + /** + * Resets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second. + * @hide + */ + @TestApi + public void resetDriftUncertaintyNanosPerSecond() { + resetFlag(HAS_DRIFT_UNCERTAINTY); + mDriftUncertaintyNanosPerSecond = Double.NaN; + } + + /** + * Gets count of hardware clock discontinuities. + * + * <p>When this value stays the same, vs. a value in a previously reported {@link GnssClock}, it + * can be safely assumed that the {@code TimeNanos} value has been derived from a clock that has + * been running continuously - e.g. a single continuously powered crystal oscillator, and thus + * the {@code (FullBiasNanos + BiasNanos)} offset can be modelled with traditional clock bias + * & drift models. + * + * <p>Each time this value changes, vs. the value in a previously reported {@link GnssClock}, + * that suggests the hardware clock may have experienced a discontinuity (e.g. a power cycle or + * other anomaly), so that any assumptions about modelling a smoothly changing + * {@code (FullBiasNanos + BiasNanos)} offset, and a smoothly growing {@code (TimeNanos)} + * between this and the previously reported {@code GnssClock}, should be reset. + */ + public int getHardwareClockDiscontinuityCount() { + return mHardwareClockDiscontinuityCount; + } + + /** + * Sets count of last hardware clock discontinuity. + * @hide + */ + @TestApi + public void setHardwareClockDiscontinuityCount(int value) { + mHardwareClockDiscontinuityCount = value; + } + + public static final Creator<GnssClock> CREATOR = new Creator<GnssClock>() { + @Override + public GnssClock createFromParcel(Parcel parcel) { + GnssClock gpsClock = new GnssClock(); + + gpsClock.mFlags = parcel.readInt(); + gpsClock.mLeapSecond = parcel.readInt(); + gpsClock.mTimeNanos = parcel.readLong(); + gpsClock.mTimeUncertaintyNanos = parcel.readDouble(); + gpsClock.mFullBiasNanos = parcel.readLong(); + gpsClock.mBiasNanos = parcel.readDouble(); + gpsClock.mBiasUncertaintyNanos = parcel.readDouble(); + gpsClock.mDriftNanosPerSecond = parcel.readDouble(); + gpsClock.mDriftUncertaintyNanosPerSecond = parcel.readDouble(); + gpsClock.mHardwareClockDiscontinuityCount = parcel.readInt(); + + return gpsClock; + } + + @Override + public GnssClock[] newArray(int size) { + return new GnssClock[size]; + } + }; + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mFlags); + parcel.writeInt(mLeapSecond); + parcel.writeLong(mTimeNanos); + parcel.writeDouble(mTimeUncertaintyNanos); + parcel.writeLong(mFullBiasNanos); + parcel.writeDouble(mBiasNanos); + parcel.writeDouble(mBiasUncertaintyNanos); + parcel.writeDouble(mDriftNanosPerSecond); + parcel.writeDouble(mDriftUncertaintyNanosPerSecond); + parcel.writeInt(mHardwareClockDiscontinuityCount); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + final String format = " %-15s = %s\n"; + final String formatWithUncertainty = " %-15s = %-25s %-26s = %s\n"; + StringBuilder builder = new StringBuilder("GnssClock:\n"); + + builder.append(String.format(format, "LeapSecond", hasLeapSecond() ? mLeapSecond : null)); + + builder.append(String.format( + formatWithUncertainty, + "TimeNanos", + mTimeNanos, + "TimeUncertaintyNanos", + hasTimeUncertaintyNanos() ? mTimeUncertaintyNanos : null)); + + builder.append(String.format( + format, + "FullBiasNanos", + hasFullBiasNanos() ? mFullBiasNanos : null)); + + builder.append(String.format( + formatWithUncertainty, + "BiasNanos", + hasBiasNanos() ? mBiasNanos : null, + "BiasUncertaintyNanos", + hasBiasUncertaintyNanos() ? mBiasUncertaintyNanos : null)); + + builder.append(String.format( + formatWithUncertainty, + "DriftNanosPerSecond", + hasDriftNanosPerSecond() ? mDriftNanosPerSecond : null, + "DriftUncertaintyNanosPerSecond", + hasDriftUncertaintyNanosPerSecond() ? mDriftUncertaintyNanosPerSecond : null)); + + builder.append(String.format( + format, + "HardwareClockDiscontinuityCount", + mHardwareClockDiscontinuityCount)); + + return builder.toString(); + } + + private void initialize() { + mFlags = HAS_NO_FLAGS; + resetLeapSecond(); + setTimeNanos(Long.MIN_VALUE); + resetTimeUncertaintyNanos(); + resetFullBiasNanos(); + resetBiasNanos(); + resetBiasUncertaintyNanos(); + resetDriftNanosPerSecond(); + resetDriftUncertaintyNanosPerSecond(); + setHardwareClockDiscontinuityCount(Integer.MIN_VALUE); + } + + private void setFlag(int flag) { + mFlags |= flag; + } + + private void resetFlag(int flag) { + mFlags &= ~flag; + } + + private boolean isFlagSet(int flag) { + return (mFlags & flag) == flag; + } +} diff --git a/android/location/GnssMeasurement.java b/android/location/GnssMeasurement.java new file mode 100644 index 00000000..d24a4774 --- /dev/null +++ b/android/location/GnssMeasurement.java @@ -0,0 +1,1107 @@ +/* + * Copyright (C) 2014 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.TestApi; +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A class representing a GNSS satellite measurement, containing raw and computed information. + */ +public final class GnssMeasurement implements Parcelable { + private int mFlags; + private int mSvid; + private int mConstellationType; + private double mTimeOffsetNanos; + private int mState; + private long mReceivedSvTimeNanos; + private long mReceivedSvTimeUncertaintyNanos; + private double mCn0DbHz; + private double mPseudorangeRateMetersPerSecond; + private double mPseudorangeRateUncertaintyMetersPerSecond; + private int mAccumulatedDeltaRangeState; + private double mAccumulatedDeltaRangeMeters; + private double mAccumulatedDeltaRangeUncertaintyMeters; + private float mCarrierFrequencyHz; + private long mCarrierCycles; + private double mCarrierPhase; + private double mCarrierPhaseUncertainty; + private int mMultipathIndicator; + private double mSnrInDb; + private double mAutomaticGainControlLevelInDb; + + // The following enumerations must be in sync with the values declared in gps.h + + private static final int HAS_NO_FLAGS = 0; + private static final int HAS_SNR = (1<<0); + private static final int HAS_CARRIER_FREQUENCY = (1<<9); + private static final int HAS_CARRIER_CYCLES = (1<<10); + private static final int HAS_CARRIER_PHASE = (1<<11); + private static final int HAS_CARRIER_PHASE_UNCERTAINTY = (1<<12); + private static final int HAS_AUTOMATIC_GAIN_CONTROL = (1<<13); + + /** + * The status of the multipath indicator. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({MULTIPATH_INDICATOR_UNKNOWN, MULTIPATH_INDICATOR_DETECTED, + MULTIPATH_INDICATOR_NOT_DETECTED}) + public @interface MultipathIndicator {} + + /** + * The indicator is not available or the presence or absence of multipath is unknown. + */ + public static final int MULTIPATH_INDICATOR_UNKNOWN = 0; + + /** + * The measurement shows signs of multi-path. + */ + public static final int MULTIPATH_INDICATOR_DETECTED = 1; + + /** + * The measurement shows no signs of multi-path. + */ + public static final int MULTIPATH_INDICATOR_NOT_DETECTED = 2; + + /** This GNSS measurement's tracking state is invalid or unknown. */ + public static final int STATE_UNKNOWN = 0; + /** This GNSS measurement's tracking state has code lock. */ + public static final int STATE_CODE_LOCK = (1<<0); + /** This GNSS measurement's tracking state has bit sync. */ + public static final int STATE_BIT_SYNC = (1<<1); + /** This GNSS measurement's tracking state has sub-frame sync. */ + public static final int STATE_SUBFRAME_SYNC = (1<<2); + /** This GNSS measurement's tracking state has time-of-week decoded. */ + public static final int STATE_TOW_DECODED = (1<<3); + /** This GNSS measurement's tracking state contains millisecond ambiguity. */ + public static final int STATE_MSEC_AMBIGUOUS = (1<<4); + /** This GNSS measurement's tracking state has symbol sync. */ + public static final int STATE_SYMBOL_SYNC = (1<<5); + /** This Glonass measurement's tracking state has string sync. */ + public static final int STATE_GLO_STRING_SYNC = (1<<6); + /** This Glonass measurement's tracking state has time-of-day decoded. */ + public static final int STATE_GLO_TOD_DECODED = (1<<7); + /** This Beidou measurement's tracking state has D2 bit sync. */ + public static final int STATE_BDS_D2_BIT_SYNC = (1<<8); + /** This Beidou measurement's tracking state has D2 sub-frame sync. */ + public static final int STATE_BDS_D2_SUBFRAME_SYNC = (1<<9); + /** This Galileo measurement's tracking state has E1B/C code lock. */ + public static final int STATE_GAL_E1BC_CODE_LOCK = (1<<10); + /** This Galileo measurement's tracking state has E1C secondary code lock. */ + public static final int STATE_GAL_E1C_2ND_CODE_LOCK = (1<<11); + /** This Galileo measurement's tracking state has E1B page sync. */ + public static final int STATE_GAL_E1B_PAGE_SYNC = (1<<12); + /** This SBAS measurement's tracking state has whole second level sync. */ + public static final int STATE_SBAS_SYNC = (1<<13); + /** + * This GNSS measurement's tracking state has time-of-week known, possibly not decoded + * over the air but has been determined from other sources. If TOW decoded is set then TOW Known + * will also be set. + */ + public static final int STATE_TOW_KNOWN = (1<<14); + /** + * This Glonass measurement's tracking state has time-of-day known, possibly not decoded + * over the air but has been determined from other sources. If TOD decoded is set then TOD Known + * will also be set. + */ + public static final int STATE_GLO_TOD_KNOWN = (1<<15); + + /** + * All the GNSS receiver state flags, for bit masking purposes (not a sensible state for any + * individual measurement.) + */ + private static final int STATE_ALL = 0x3fff; // 2 bits + 4 bits + 4 bits + 4 bits = 14 bits + + /** + * The state of the 'Accumulated Delta Range' is invalid or unknown. + */ + public static final int ADR_STATE_UNKNOWN = 0; + + /** + * The state of the 'Accumulated Delta Range' is valid. + */ + public static final int ADR_STATE_VALID = (1<<0); + + /** + * The state of the 'Accumulated Delta Range' has detected a reset. + */ + public static final int ADR_STATE_RESET = (1<<1); + + /** + * The state of the 'Accumulated Delta Range' has a cycle slip detected. + */ + public static final int ADR_STATE_CYCLE_SLIP = (1<<2); + + /** + * All the 'Accumulated Delta Range' flags. + */ + private static final int ADR_ALL = ADR_STATE_VALID | ADR_STATE_RESET | ADR_STATE_CYCLE_SLIP; + + // End enumerations in sync with gps.h + + /** + * @hide + */ + @TestApi + public GnssMeasurement() { + initialize(); + } + + /** + * Sets all contents to the values stored in the provided object. + * @hide + */ + @TestApi + public void set(GnssMeasurement measurement) { + mFlags = measurement.mFlags; + mSvid = measurement.mSvid; + mConstellationType = measurement.mConstellationType; + mTimeOffsetNanos = measurement.mTimeOffsetNanos; + mState = measurement.mState; + mReceivedSvTimeNanos = measurement.mReceivedSvTimeNanos; + mReceivedSvTimeUncertaintyNanos = measurement.mReceivedSvTimeUncertaintyNanos; + mCn0DbHz = measurement.mCn0DbHz; + mPseudorangeRateMetersPerSecond = measurement.mPseudorangeRateMetersPerSecond; + mPseudorangeRateUncertaintyMetersPerSecond = + measurement.mPseudorangeRateUncertaintyMetersPerSecond; + mAccumulatedDeltaRangeState = measurement.mAccumulatedDeltaRangeState; + mAccumulatedDeltaRangeMeters = measurement.mAccumulatedDeltaRangeMeters; + mAccumulatedDeltaRangeUncertaintyMeters = + measurement.mAccumulatedDeltaRangeUncertaintyMeters; + mCarrierFrequencyHz = measurement.mCarrierFrequencyHz; + mCarrierCycles = measurement.mCarrierCycles; + mCarrierPhase = measurement.mCarrierPhase; + mCarrierPhaseUncertainty = measurement.mCarrierPhaseUncertainty; + mMultipathIndicator = measurement.mMultipathIndicator; + mSnrInDb = measurement.mSnrInDb; + mAutomaticGainControlLevelInDb = measurement.mAutomaticGainControlLevelInDb; + } + + /** + * Resets all the contents to its original state. + * @hide + */ + @TestApi + public void reset() { + initialize(); + } + + /** + * Gets the satellite ID. + * + * <p>Interpretation depends on {@link #getConstellationType()}. + * See {@link GnssStatus#getSvid(int)}. + */ + public int getSvid() { + return mSvid; + } + + /** + * Sets the Satellite ID. + * @hide + */ + @TestApi + public void setSvid(int value) { + mSvid = value; + } + + /** + * Gets the constellation type. + * + * <p>The return value is one of those constants with {@code CONSTELLATION_} prefix in + * {@link GnssStatus}. + */ + @GnssStatus.ConstellationType + public int getConstellationType() { + return mConstellationType; + } + + /** + * Sets the constellation type. + * @hide + */ + @TestApi + public void setConstellationType(@GnssStatus.ConstellationType int value) { + mConstellationType = value; + } + + /** + * Gets the time offset at which the measurement was taken in nanoseconds. + * + * <p>The reference receiver's time from which this is offset is specified by + * {@link GnssClock#getTimeNanos()}. + * + * <p>The sign of this value is given by the following equation: + * <pre> + * measurement time = TimeNanos + TimeOffsetNanos</pre> + * + * <p>The value provides an individual time-stamp for the measurement, and allows sub-nanosecond + * accuracy. + */ + public double getTimeOffsetNanos() { + return mTimeOffsetNanos; + } + + /** + * Sets the time offset at which the measurement was taken in nanoseconds. + * @hide + */ + @TestApi + public void setTimeOffsetNanos(double value) { + mTimeOffsetNanos = value; + } + + /** + * Gets per-satellite sync state. + * + * <p>It represents the current sync state for the associated satellite. + * + * <p>This value helps interpret {@link #getReceivedSvTimeNanos()}. + */ + public int getState() { + return mState; + } + + /** + * Sets the sync state. + * @hide + */ + @TestApi + public void setState(int value) { + mState = value; + } + + /** + * Gets a string representation of the 'sync state'. + * + * <p>For internal and logging use only. + */ + private String getStateString() { + if (mState == STATE_UNKNOWN) { + return "Unknown"; + } + + StringBuilder builder = new StringBuilder(); + if ((mState & STATE_CODE_LOCK) != 0) { + builder.append("CodeLock|"); + } + if ((mState & STATE_BIT_SYNC) != 0) { + builder.append("BitSync|"); + } + if ((mState & STATE_SUBFRAME_SYNC) != 0) { + builder.append("SubframeSync|"); + } + if ((mState & STATE_TOW_DECODED) != 0) { + builder.append("TowDecoded|"); + } + if ((mState & STATE_TOW_KNOWN) != 0) { + builder.append("TowKnown|"); + } + if ((mState & STATE_MSEC_AMBIGUOUS) != 0) { + builder.append("MsecAmbiguous|"); + } + if ((mState & STATE_SYMBOL_SYNC) != 0) { + builder.append("SymbolSync|"); + } + if ((mState & STATE_GLO_STRING_SYNC) != 0) { + builder.append("GloStringSync|"); + } + if ((mState & STATE_GLO_TOD_DECODED) != 0) { + builder.append("GloTodDecoded|"); + } + if ((mState & STATE_GLO_TOD_KNOWN) != 0) { + builder.append("GloTodKnown|"); + } + if ((mState & STATE_BDS_D2_BIT_SYNC) != 0) { + builder.append("BdsD2BitSync|"); + } + if ((mState & STATE_BDS_D2_SUBFRAME_SYNC) != 0) { + builder.append("BdsD2SubframeSync|"); + } + if ((mState & STATE_GAL_E1BC_CODE_LOCK) != 0) { + builder.append("GalE1bcCodeLock|"); + } + if ((mState & STATE_GAL_E1C_2ND_CODE_LOCK) != 0) { + builder.append("E1c2ndCodeLock|"); + } + if ((mState & STATE_GAL_E1B_PAGE_SYNC) != 0) { + builder.append("GalE1bPageSync|"); + } + if ((mState & STATE_SBAS_SYNC) != 0) { + builder.append("SbasSync|"); + } + + int remainingStates = mState & ~STATE_ALL; + if (remainingStates > 0) { + builder.append("Other("); + builder.append(Integer.toBinaryString(remainingStates)); + builder.append(")|"); + } + builder.setLength(builder.length() - 1); + return builder.toString(); + } + + /** + * Gets the received GNSS satellite time, at the measurement time, in nanoseconds. + * + * <p>For GPS & QZSS, this is: + * <ul> + * <li>Received GPS Time-of-Week at the measurement time, in nanoseconds.</li> + * <li>The value is relative to the beginning of the current GPS week.</li> + * </ul> + * + * <p>Given the highest sync state that can be achieved, per each satellite, valid range + * for this field can be: + * <pre> + * Searching : [ 0 ] : STATE_UNKNOWN + * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set + * Bit sync : [ 0 20ms ] : STATE_BIT_SYNC is set + * Subframe sync : [ 0 6s ] : STATE_SUBFRAME_SYNC is set + * TOW decoded : [ 0 1week ] : STATE_TOW_DECODED is set + * TOW Known : [ 0 1week ] : STATE_TOW_KNOWN set</pre> + * + * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has + * been determined from other sources. If TOW decoded is set then TOW Known must also be set. + * + * <p>Note well: if there is any ambiguity in integer millisecond, {@code STATE_MSEC_AMBIGUOUS} + * must be set accordingly, in the 'state' field. + * + * <p>This value must be populated if 'state' != {@code STATE_UNKNOWN}. + * + * <p>For Glonass, this is: + * <ul> + * <li>Received Glonass time of day, at the measurement time in nanoseconds.</li> + * </ul> + * + * <p>Given the highest sync state that can be achieved, per each satellite, valid range for + * this field can be: + * <pre> + * Searching : [ 0 ] : STATE_UNKNOWN + * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set + * Symbol sync : [ 0 10ms ] : STATE_SYMBOL_SYNC is set + * Bit sync : [ 0 20ms ] : STATE_BIT_SYNC is set + * String sync : [ 0 2s ] : STATE_GLO_STRING_SYNC is set + * Time of day decoded : [ 0 1day ] : STATE_GLO_TOD_DECODED is set + * Time of day known : [ 0 1day ] : STATE_GLO_TOD_KNOWN set</pre> + * + * Note: Time of day known refers to the case where it is possibly not decoded over the air but + * has been determined from other sources. If Time of day decoded is set then Time of day known + * must also be set. + * + * <p>For Beidou, this is: + * <ul> + * <li>Received Beidou time of week, at the measurement time in nanoseconds.</li> + * </ul> + * + * <p>Given the highest sync state that can be achieved, per each satellite, valid range for + * this field can be: + * <pre> + * Searching : [ 0 ] : STATE_UNKNOWN + * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set + * Bit sync (D2) : [ 0 2ms ] : STATE_BDS_D2_BIT_SYNC is set + * Bit sync (D1) : [ 0 20ms ] : STATE_BIT_SYNC is set + * Subframe (D2) : [ 0 0.6s ] : STATE_BDS_D2_SUBFRAME_SYNC is set + * Subframe (D1) : [ 0 6s ] : STATE_SUBFRAME_SYNC is set + * Time of week decoded : [ 0 1week ] : STATE_TOW_DECODED is set + * Time of week known : [ 0 1week ] : STATE_TOW_KNOWN set</pre> + * + * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has + * been determined from other sources. If TOW decoded is set then TOW Known must also be set. + * + * <p>For Galileo, this is: + * <ul> + * <li>Received Galileo time of week, at the measurement time in nanoseconds.</li> + * </ul> + * <pre> + * E1BC code lock : [ 0 4ms ] : STATE_GAL_E1BC_CODE_LOCK is set + * E1C 2nd code lock : [ 0 100ms ] : STATE_GAL_E1C_2ND_CODE_LOCK is set + * E1B page : [ 0 2s ] : STATE_GAL_E1B_PAGE_SYNC is set + * Time of week decoded : [ 0 1week ] : STATE_TOW_DECODED is set + * Time of week known : [ 0 1week ] : STATE_TOW_KNOWN set</pre> + * + * Note: TOW Known refers to the case where TOW is possibly not decoded over the air but has + * been determined from other sources. If TOW decoded is set then TOW Known must also be set. + * + * <p>For SBAS, this is: + * <ul> + * <li>Received SBAS time, at the measurement time in nanoseconds.</li> + * </ul> + * + * <p>Given the highest sync state that can be achieved, per each satellite, valid range for + * this field can be: + * <pre> + * Searching : [ 0 ] : STATE_UNKNOWN + * C/A code lock : [ 0 1ms ] : STATE_CODE_LOCK is set + * Symbol sync : [ 0 2ms ] : STATE_SYMBOL_SYNC is set + * Message : [ 0 1s ] : STATE_SBAS_SYNC is set</pre> + */ + public long getReceivedSvTimeNanos() { + return mReceivedSvTimeNanos; + } + + /** + * Sets the received GNSS time in nanoseconds. + * @hide + */ + @TestApi + public void setReceivedSvTimeNanos(long value) { + mReceivedSvTimeNanos = value; + } + + /** + * Gets the error estimate (1-sigma) for the received GNSS time, in nanoseconds. + */ + public long getReceivedSvTimeUncertaintyNanos() { + return mReceivedSvTimeUncertaintyNanos; + } + + /** + * Sets the received GNSS time uncertainty (1-Sigma) in nanoseconds. + * @hide + */ + @TestApi + public void setReceivedSvTimeUncertaintyNanos(long value) { + mReceivedSvTimeUncertaintyNanos = value; + } + + /** + * Gets the Carrier-to-noise density in dB-Hz. + * + * <p>Typical range: 10-50 db-Hz. + * + * <p>The value contains the measured C/N0 for the signal at the antenna input. + */ + public double getCn0DbHz() { + return mCn0DbHz; + } + + /** + * Sets the carrier-to-noise density in dB-Hz. + * @hide + */ + @TestApi + public void setCn0DbHz(double value) { + mCn0DbHz = value; + } + + /** + * Gets the Pseudorange rate at the timestamp in m/s. + * + * <p>The error estimate for this value is + * {@link #getPseudorangeRateUncertaintyMetersPerSecond()}. + * + * <p>The value is uncorrected, i.e. corrections for receiver and satellite clock frequency + * errors are not included. + * + * <p>A positive 'uncorrected' value indicates that the SV is moving away from the receiver. The + * sign of the 'uncorrected' 'pseudorange rate' and its relation to the sign of 'doppler shift' + * is given by the equation: + * + * <pre> + * pseudorange rate = -k * doppler shift (where k is a constant)</pre> + */ + public double getPseudorangeRateMetersPerSecond() { + return mPseudorangeRateMetersPerSecond; + } + + /** + * Sets the pseudorange rate at the timestamp in m/s. + * @hide + */ + @TestApi + public void setPseudorangeRateMetersPerSecond(double value) { + mPseudorangeRateMetersPerSecond = value; + } + + /** + * Gets the pseudorange's rate uncertainty (1-Sigma) in m/s. + * + * <p>The uncertainty is represented as an absolute (single sided) value. + */ + public double getPseudorangeRateUncertaintyMetersPerSecond() { + return mPseudorangeRateUncertaintyMetersPerSecond; + } + + /** + * Sets the pseudorange's rate uncertainty (1-Sigma) in m/s. + * @hide + */ + @TestApi + public void setPseudorangeRateUncertaintyMetersPerSecond(double value) { + mPseudorangeRateUncertaintyMetersPerSecond = value; + } + + /** + * Gets 'Accumulated Delta Range' state. + * + * <p>It indicates whether {@link #getAccumulatedDeltaRangeMeters()} is reset or there is a + * cycle slip (indicating 'loss of lock'). + */ + public int getAccumulatedDeltaRangeState() { + return mAccumulatedDeltaRangeState; + } + + /** + * Sets the 'Accumulated Delta Range' state. + * @hide + */ + @TestApi + public void setAccumulatedDeltaRangeState(int value) { + mAccumulatedDeltaRangeState = value; + } + + /** + * Gets a string representation of the 'Accumulated Delta Range state'. + * + * <p>For internal and logging use only. + */ + private String getAccumulatedDeltaRangeStateString() { + if (mAccumulatedDeltaRangeState == ADR_STATE_UNKNOWN) { + return "Unknown"; + } + StringBuilder builder = new StringBuilder(); + if ((mAccumulatedDeltaRangeState & ADR_STATE_VALID) == ADR_STATE_VALID) { + builder.append("Valid|"); + } + if ((mAccumulatedDeltaRangeState & ADR_STATE_RESET) == ADR_STATE_RESET) { + builder.append("Reset|"); + } + if ((mAccumulatedDeltaRangeState & ADR_STATE_CYCLE_SLIP) == ADR_STATE_CYCLE_SLIP) { + builder.append("CycleSlip|"); + } + int remainingStates = mAccumulatedDeltaRangeState & ~ADR_ALL; + if (remainingStates > 0) { + builder.append("Other("); + builder.append(Integer.toBinaryString(remainingStates)); + builder.append(")|"); + } + builder.deleteCharAt(builder.length() - 1); + return builder.toString(); + } + + /** + * Gets the accumulated delta range since the last channel reset, in meters. + * + * <p>The error estimate for this value is {@link #getAccumulatedDeltaRangeUncertaintyMeters()}. + * + * <p>The availability of the value is represented by {@link #getAccumulatedDeltaRangeState()}. + * + * <p>A positive value indicates that the SV is moving away from the receiver. + * The sign of {@link #getAccumulatedDeltaRangeMeters()} and its relation to the sign of + * {@link #getCarrierPhase()} is given by the equation: + * + * <pre> + * accumulated delta range = -k * carrier phase (where k is a constant)</pre> + */ + public double getAccumulatedDeltaRangeMeters() { + return mAccumulatedDeltaRangeMeters; + } + + /** + * Sets the accumulated delta range in meters. + * @hide + */ + @TestApi + public void setAccumulatedDeltaRangeMeters(double value) { + mAccumulatedDeltaRangeMeters = value; + } + + /** + * Gets the accumulated delta range's uncertainty (1-Sigma) in meters. + * + * <p>The uncertainty is represented as an absolute (single sided) value. + * + * <p>The status of the value is represented by {@link #getAccumulatedDeltaRangeState()}. + */ + public double getAccumulatedDeltaRangeUncertaintyMeters() { + return mAccumulatedDeltaRangeUncertaintyMeters; + } + + /** + * Sets the accumulated delta range's uncertainty (1-sigma) in meters. + * + * <p>The status of the value is represented by {@link #getAccumulatedDeltaRangeState()}. + * + * @hide + */ + @TestApi + public void setAccumulatedDeltaRangeUncertaintyMeters(double value) { + mAccumulatedDeltaRangeUncertaintyMeters = value; + } + + /** + * Returns {@code true} if {@link #getCarrierFrequencyHz()} is available, {@code false} + * otherwise. + */ + public boolean hasCarrierFrequencyHz() { + return isFlagSet(HAS_CARRIER_FREQUENCY); + } + + /** + * Gets the carrier frequency of the tracked signal. + * + * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz, + * L5 = 1176.45 MHz, varying GLO channels, etc. If the field is not set, it is the primary + * common use central frequency, e.g. L1 = 1575.45 MHz for GPS. + * + * For an L1, L5 receiver tracking a satellite on L1 and L5 at the same time, two raw + * measurement objects will be reported for this same satellite, in one of the measurement + * objects, all the values related to L1 will be filled, and in the other all of the values + * related to L5 will be filled. + * + * <p>The value is only available if {@link #hasCarrierFrequencyHz()} is {@code true}. + * + * @return the carrier frequency of the signal tracked in Hz. + */ + public float getCarrierFrequencyHz() { + return mCarrierFrequencyHz; + } + + /** + * Sets the Carrier frequency in Hz. + * @hide + */ + @TestApi + public void setCarrierFrequencyHz(float carrierFrequencyHz) { + setFlag(HAS_CARRIER_FREQUENCY); + mCarrierFrequencyHz = carrierFrequencyHz; + } + + /** + * Resets the Carrier frequency in Hz. + * @hide + */ + @TestApi + public void resetCarrierFrequencyHz() { + resetFlag(HAS_CARRIER_FREQUENCY); + mCarrierFrequencyHz = Float.NaN; + } + + /** + * Returns {@code true} if {@link #getCarrierCycles()} is available, {@code false} otherwise. + */ + public boolean hasCarrierCycles() { + return isFlagSet(HAS_CARRIER_CYCLES); + } + + /** + * The number of full carrier cycles between the satellite and the receiver. + * + * <p>The reference frequency is given by the value of {@link #getCarrierFrequencyHz()}. + * + * <p>The value is only available if {@link #hasCarrierCycles()} is {@code true}. + */ + public long getCarrierCycles() { + return mCarrierCycles; + } + + /** + * Sets the number of full carrier cycles between the satellite and the receiver. + * @hide + */ + @TestApi + public void setCarrierCycles(long value) { + setFlag(HAS_CARRIER_CYCLES); + mCarrierCycles = value; + } + + /** + * Resets the number of full carrier cycles between the satellite and the receiver. + * @hide + */ + @TestApi + public void resetCarrierCycles() { + resetFlag(HAS_CARRIER_CYCLES); + mCarrierCycles = Long.MIN_VALUE; + } + + /** + * Returns {@code true} if {@link #getCarrierPhase()} is available, {@code false} otherwise. + */ + public boolean hasCarrierPhase() { + return isFlagSet(HAS_CARRIER_PHASE); + } + + /** + * Gets the RF phase detected by the receiver. + * + * <p>Range: [0.0, 1.0]. + * + * <p>This is the fractional part of the complete carrier phase measurement. + * + * <p>The reference frequency is given by the value of {@link #getCarrierFrequencyHz()}. + * + * <p>The error estimate for this value is {@link #getCarrierPhaseUncertainty()}. + * + * <p>The value is only available if {@link #hasCarrierPhase()} is {@code true}. + */ + public double getCarrierPhase() { + return mCarrierPhase; + } + + /** + * Sets the RF phase detected by the receiver. + * @hide + */ + @TestApi + public void setCarrierPhase(double value) { + setFlag(HAS_CARRIER_PHASE); + mCarrierPhase = value; + } + + /** + * Resets the RF phase detected by the receiver. + * @hide + */ + @TestApi + public void resetCarrierPhase() { + resetFlag(HAS_CARRIER_PHASE); + mCarrierPhase = Double.NaN; + } + + /** + * Returns {@code true} if {@link #getCarrierPhaseUncertainty()} is available, {@code false} + * otherwise. + */ + public boolean hasCarrierPhaseUncertainty() { + return isFlagSet(HAS_CARRIER_PHASE_UNCERTAINTY); + } + + /** + * Gets the carrier-phase's uncertainty (1-Sigma). + * + * <p>The uncertainty is represented as an absolute (single sided) value. + * + * <p>The value is only available if {@link #hasCarrierPhaseUncertainty()} is {@code true}. + */ + public double getCarrierPhaseUncertainty() { + return mCarrierPhaseUncertainty; + } + + /** + * Sets the Carrier-phase's uncertainty (1-Sigma) in cycles. + * @hide + */ + @TestApi + public void setCarrierPhaseUncertainty(double value) { + setFlag(HAS_CARRIER_PHASE_UNCERTAINTY); + mCarrierPhaseUncertainty = value; + } + + /** + * Resets the Carrier-phase's uncertainty (1-Sigma) in cycles. + * @hide + */ + @TestApi + public void resetCarrierPhaseUncertainty() { + resetFlag(HAS_CARRIER_PHASE_UNCERTAINTY); + mCarrierPhaseUncertainty = Double.NaN; + } + + /** + * Gets a value indicating the 'multipath' state of the event. + */ + @MultipathIndicator + public int getMultipathIndicator() { + return mMultipathIndicator; + } + + /** + * Sets the 'multi-path' indicator. + * @hide + */ + @TestApi + public void setMultipathIndicator(@MultipathIndicator int value) { + mMultipathIndicator = value; + } + + /** + * Gets a string representation of the 'multi-path indicator'. + * + * <p>For internal and logging use only. + */ + private String getMultipathIndicatorString() { + switch(mMultipathIndicator) { + case MULTIPATH_INDICATOR_UNKNOWN: + return "Unknown"; + case MULTIPATH_INDICATOR_DETECTED: + return "Detected"; + case MULTIPATH_INDICATOR_NOT_DETECTED: + return "NotDetected"; + default: + return "<Invalid:" + mMultipathIndicator + ">"; + } + } + + /** + * Returns {@code true} if {@link #getSnrInDb()} is available, {@code false} otherwise. + */ + public boolean hasSnrInDb() { + return isFlagSet(HAS_SNR); + } + + /** + * Gets the Signal-to-Noise ratio (SNR) in dB. + * + * <p>The value is only available if {@link #hasSnrInDb()} is {@code true}. + */ + public double getSnrInDb() { + return mSnrInDb; + } + + /** + * Sets the Signal-to-noise ratio (SNR) in dB. + * @hide + */ + @TestApi + public void setSnrInDb(double snrInDb) { + setFlag(HAS_SNR); + mSnrInDb = snrInDb; + } + + /** + * Resets the Signal-to-noise ratio (SNR) in dB. + * @hide + */ + @TestApi + public void resetSnrInDb() { + resetFlag(HAS_SNR); + mSnrInDb = Double.NaN; + } + + /** + * Returns {@code true} if {@link #getAutomaticGainControlLevelDb()} is available, + * {@code false} otherwise. + */ + public boolean hasAutomaticGainControlLevelDb() { + return isFlagSet(HAS_AUTOMATIC_GAIN_CONTROL); + } + + /** + * Gets the Automatic Gain Control level in dB. + * + * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC + * level may be used to indicate potential interference. When AGC is at a nominal level, this + * value must be set as 0. Higher gain (and/or lower input power) shall be output as a positive + * number. Hence in cases of strong jamming, in the band of this signal, this value will go more + * negative. + * + * <p>Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW + * components) may also affect the typical output of of this value on any given hardware design + * in an open sky test - the important aspect of this output is that changes in this value are + * indicative of changes on input signal power in the frequency band for this measurement. + * <p>The value is only available if {@link #hasAutomaticGainControlLevelDb()} is {@code true} + */ + public double getAutomaticGainControlLevelDb() { + return mAutomaticGainControlLevelInDb; + } + + /** + * Sets the Automatic Gain Control level in dB. + * @hide + */ + @TestApi + public void setAutomaticGainControlLevelInDb(double agcLevelDb) { + setFlag(HAS_AUTOMATIC_GAIN_CONTROL); + mAutomaticGainControlLevelInDb = agcLevelDb; + } + + /** + * Resets the Automatic Gain Control level. + * @hide + */ + @TestApi + public void resetAutomaticGainControlLevel() { + resetFlag(HAS_AUTOMATIC_GAIN_CONTROL); + mAutomaticGainControlLevelInDb = Double.NaN; + } + + public static final Creator<GnssMeasurement> CREATOR = new Creator<GnssMeasurement>() { + @Override + public GnssMeasurement createFromParcel(Parcel parcel) { + GnssMeasurement gnssMeasurement = new GnssMeasurement(); + + gnssMeasurement.mFlags = parcel.readInt(); + gnssMeasurement.mSvid = parcel.readInt(); + gnssMeasurement.mConstellationType = parcel.readInt(); + gnssMeasurement.mTimeOffsetNanos = parcel.readDouble(); + gnssMeasurement.mState = parcel.readInt(); + gnssMeasurement.mReceivedSvTimeNanos = parcel.readLong(); + gnssMeasurement.mReceivedSvTimeUncertaintyNanos = parcel.readLong(); + gnssMeasurement.mCn0DbHz = parcel.readDouble(); + gnssMeasurement.mPseudorangeRateMetersPerSecond = parcel.readDouble(); + gnssMeasurement.mPseudorangeRateUncertaintyMetersPerSecond = parcel.readDouble(); + gnssMeasurement.mAccumulatedDeltaRangeState = parcel.readInt(); + gnssMeasurement.mAccumulatedDeltaRangeMeters = parcel.readDouble(); + gnssMeasurement.mAccumulatedDeltaRangeUncertaintyMeters = parcel.readDouble(); + gnssMeasurement.mCarrierFrequencyHz = parcel.readFloat(); + gnssMeasurement.mCarrierCycles = parcel.readLong(); + gnssMeasurement.mCarrierPhase = parcel.readDouble(); + gnssMeasurement.mCarrierPhaseUncertainty = parcel.readDouble(); + gnssMeasurement.mMultipathIndicator = parcel.readInt(); + gnssMeasurement.mSnrInDb = parcel.readDouble(); + gnssMeasurement.mAutomaticGainControlLevelInDb = parcel.readDouble(); + + return gnssMeasurement; + } + + @Override + public GnssMeasurement[] newArray(int i) { + return new GnssMeasurement[i]; + } + }; + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mFlags); + parcel.writeInt(mSvid); + parcel.writeInt(mConstellationType); + parcel.writeDouble(mTimeOffsetNanos); + parcel.writeInt(mState); + parcel.writeLong(mReceivedSvTimeNanos); + parcel.writeLong(mReceivedSvTimeUncertaintyNanos); + parcel.writeDouble(mCn0DbHz); + parcel.writeDouble(mPseudorangeRateMetersPerSecond); + parcel.writeDouble(mPseudorangeRateUncertaintyMetersPerSecond); + parcel.writeInt(mAccumulatedDeltaRangeState); + parcel.writeDouble(mAccumulatedDeltaRangeMeters); + parcel.writeDouble(mAccumulatedDeltaRangeUncertaintyMeters); + parcel.writeFloat(mCarrierFrequencyHz); + parcel.writeLong(mCarrierCycles); + parcel.writeDouble(mCarrierPhase); + parcel.writeDouble(mCarrierPhaseUncertainty); + parcel.writeInt(mMultipathIndicator); + parcel.writeDouble(mSnrInDb); + parcel.writeDouble(mAutomaticGainControlLevelInDb); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + final String format = " %-29s = %s\n"; + final String formatWithUncertainty = " %-29s = %-25s %-40s = %s\n"; + StringBuilder builder = new StringBuilder("GnssMeasurement:\n"); + + builder.append(String.format(format, "Svid", mSvid)); + builder.append(String.format(format, "ConstellationType", mConstellationType)); + builder.append(String.format(format, "TimeOffsetNanos", mTimeOffsetNanos)); + + builder.append(String.format(format, "State", getStateString())); + + builder.append(String.format( + formatWithUncertainty, + "ReceivedSvTimeNanos", + mReceivedSvTimeNanos, + "ReceivedSvTimeUncertaintyNanos", + mReceivedSvTimeUncertaintyNanos)); + + builder.append(String.format(format, "Cn0DbHz", mCn0DbHz)); + + builder.append(String.format( + formatWithUncertainty, + "PseudorangeRateMetersPerSecond", + mPseudorangeRateMetersPerSecond, + "PseudorangeRateUncertaintyMetersPerSecond", + mPseudorangeRateUncertaintyMetersPerSecond)); + + builder.append(String.format( + format, + "AccumulatedDeltaRangeState", + getAccumulatedDeltaRangeStateString())); + + builder.append(String.format( + formatWithUncertainty, + "AccumulatedDeltaRangeMeters", + mAccumulatedDeltaRangeMeters, + "AccumulatedDeltaRangeUncertaintyMeters", + mAccumulatedDeltaRangeUncertaintyMeters)); + + builder.append(String.format( + format, + "CarrierFrequencyHz", + hasCarrierFrequencyHz() ? mCarrierFrequencyHz : null)); + + builder.append(String.format( + format, + "CarrierCycles", + hasCarrierCycles() ? mCarrierCycles : null)); + + builder.append(String.format( + formatWithUncertainty, + "CarrierPhase", + hasCarrierPhase() ? mCarrierPhase : null, + "CarrierPhaseUncertainty", + hasCarrierPhaseUncertainty() ? mCarrierPhaseUncertainty : null)); + + builder.append(String.format(format, "MultipathIndicator", getMultipathIndicatorString())); + + builder.append(String.format( + format, + "SnrInDb", + hasSnrInDb() ? mSnrInDb : null)); + builder.append(String.format( + format, + "AgcLevelDb", + hasAutomaticGainControlLevelDb() ? mAutomaticGainControlLevelInDb : null)); + + return builder.toString(); + } + + private void initialize() { + mFlags = HAS_NO_FLAGS; + setSvid(0); + setTimeOffsetNanos(Long.MIN_VALUE); + setState(STATE_UNKNOWN); + setReceivedSvTimeNanos(Long.MIN_VALUE); + setReceivedSvTimeUncertaintyNanos(Long.MAX_VALUE); + setCn0DbHz(Double.MIN_VALUE); + setPseudorangeRateMetersPerSecond(Double.MIN_VALUE); + setPseudorangeRateUncertaintyMetersPerSecond(Double.MIN_VALUE); + setAccumulatedDeltaRangeState(ADR_STATE_UNKNOWN); + setAccumulatedDeltaRangeMeters(Double.MIN_VALUE); + setAccumulatedDeltaRangeUncertaintyMeters(Double.MIN_VALUE); + resetCarrierFrequencyHz(); + resetCarrierCycles(); + resetCarrierPhase(); + resetCarrierPhaseUncertainty(); + setMultipathIndicator(MULTIPATH_INDICATOR_UNKNOWN); + resetSnrInDb(); + resetAutomaticGainControlLevel(); + } + + private void setFlag(int flag) { + mFlags |= flag; + } + + private void resetFlag(int flag) { + mFlags &= ~flag; + } + + private boolean isFlagSet(int flag) { + return (mFlags & flag) == flag; + } +} diff --git a/android/location/GnssMeasurementCallbackTransport.java b/android/location/GnssMeasurementCallbackTransport.java new file mode 100644 index 00000000..21f63068 --- /dev/null +++ b/android/location/GnssMeasurementCallbackTransport.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 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.content.Context; +import android.os.RemoteException; + +/** + * A handler class to manage transport callbacks for {@link GnssMeasurementsEvent.Callback}. + * + * @hide + */ +class GnssMeasurementCallbackTransport + extends LocalListenerHelper<GnssMeasurementsEvent.Callback> { + private final ILocationManager mLocationManager; + + private final IGnssMeasurementsListener mListenerTransport = new ListenerTransport(); + + public GnssMeasurementCallbackTransport(Context context, ILocationManager locationManager) { + super(context, "GnssMeasurementListenerTransport"); + mLocationManager = locationManager; + } + + @Override + protected boolean registerWithServer() throws RemoteException { + return mLocationManager.addGnssMeasurementsListener( + mListenerTransport, + getContext().getPackageName()); + } + + @Override + protected void unregisterFromServer() throws RemoteException { + mLocationManager.removeGnssMeasurementsListener(mListenerTransport); + } + + private class ListenerTransport extends IGnssMeasurementsListener.Stub { + @Override + public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) { + ListenerOperation<GnssMeasurementsEvent.Callback> operation = + new ListenerOperation<GnssMeasurementsEvent.Callback>() { + @Override + public void execute(GnssMeasurementsEvent.Callback callback) + throws RemoteException { + callback.onGnssMeasurementsReceived(event); + } + }; + foreach(operation); + } + + @Override + public void onStatusChanged(final int status) { + ListenerOperation<GnssMeasurementsEvent.Callback> operation = + new ListenerOperation<GnssMeasurementsEvent.Callback>() { + @Override + public void execute(GnssMeasurementsEvent.Callback callback) + throws RemoteException { + callback.onStatusChanged(status); + } + }; + foreach(operation); + } + } +} diff --git a/android/location/GnssMeasurementsEvent.java b/android/location/GnssMeasurementsEvent.java new file mode 100644 index 00000000..d66fd9c4 --- /dev/null +++ b/android/location/GnssMeasurementsEvent.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2014 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.TestApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * A class implementing a container for data associated with a measurement event. + * Events are delivered to registered instances of {@link Callback}. + */ +public final class GnssMeasurementsEvent implements Parcelable { + private final GnssClock mClock; + private final Collection<GnssMeasurement> mReadOnlyMeasurements; + + /** + * Used for receiving GNSS satellite measurements from the GNSS engine. + * Each measurement contains raw and computed data identifying a satellite. + * You can implement this interface and call + * {@link LocationManager#registerGnssMeasurementsCallback}. + */ + public static abstract class Callback { + /** + * The status of the GNSS measurements event. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATUS_NOT_SUPPORTED, STATUS_READY, STATUS_LOCATION_DISABLED}) + public @interface GnssMeasurementsStatus {} + + /** + * The system does not support tracking of GNSS Measurements. + * + * <p>This status will not change in the future. + */ + public static final int STATUS_NOT_SUPPORTED = 0; + + /** + * GNSS Measurements are successfully being tracked, it will receive updates once they are + * available. + */ + public static final int STATUS_READY = 1; + + /** + * GPS provider or Location is disabled, updates will not be received until they are + * enabled. + */ + public static final int STATUS_LOCATION_DISABLED = 2; + + /** + * Reports the latest collected GNSS Measurements. + */ + public void onGnssMeasurementsReceived(GnssMeasurementsEvent eventArgs) {} + + /** + * Reports the latest status of the GNSS Measurements sub-system. + */ + public void onStatusChanged(@GnssMeasurementsStatus int status) {} + } + + /** + * @hide + */ + @TestApi + public GnssMeasurementsEvent(GnssClock clock, GnssMeasurement[] measurements) { + if (clock == null) { + throw new InvalidParameterException("Parameter 'clock' must not be null."); + } + if (measurements == null || measurements.length == 0) { + mReadOnlyMeasurements = Collections.emptyList(); + } else { + Collection<GnssMeasurement> measurementCollection = Arrays.asList(measurements); + mReadOnlyMeasurements = Collections.unmodifiableCollection(measurementCollection); + } + + mClock = clock; + } + + /** + * Gets the GNSS receiver clock information associated with the measurements for the current + * event. + */ + @NonNull + public GnssClock getClock() { + return mClock; + } + + /** + * Gets a read-only collection of measurements associated with the current event. + */ + @NonNull + public Collection<GnssMeasurement> getMeasurements() { + return mReadOnlyMeasurements; + } + + public static final Creator<GnssMeasurementsEvent> CREATOR = + new Creator<GnssMeasurementsEvent>() { + @Override + public GnssMeasurementsEvent createFromParcel(Parcel in) { + ClassLoader classLoader = getClass().getClassLoader(); + + GnssClock clock = in.readParcelable(classLoader); + + int measurementsLength = in.readInt(); + GnssMeasurement[] measurementsArray = new GnssMeasurement[measurementsLength]; + in.readTypedArray(measurementsArray, GnssMeasurement.CREATOR); + + return new GnssMeasurementsEvent(clock, measurementsArray); + } + + @Override + public GnssMeasurementsEvent[] newArray(int size) { + return new GnssMeasurementsEvent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mClock, flags); + + int measurementsCount = mReadOnlyMeasurements.size(); + GnssMeasurement[] measurementsArray = + mReadOnlyMeasurements.toArray(new GnssMeasurement[measurementsCount]); + parcel.writeInt(measurementsArray.length); + parcel.writeTypedArray(measurementsArray, flags); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("[ GnssMeasurementsEvent:\n\n"); + + builder.append(mClock.toString()); + builder.append("\n"); + + for (GnssMeasurement measurement : mReadOnlyMeasurements) { + builder.append(measurement.toString()); + builder.append("\n"); + } + + builder.append("]"); + + return builder.toString(); + } +} diff --git a/android/location/GnssNavigationMessage.java b/android/location/GnssNavigationMessage.java new file mode 100644 index 00000000..c7188aa3 --- /dev/null +++ b/android/location/GnssNavigationMessage.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2014 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.TestApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; + +/** + * A class containing a GNSS satellite Navigation Message. + */ +public final class GnssNavigationMessage implements Parcelable { + + private static final byte[] EMPTY_ARRAY = new byte[0]; + + /** + * The type of the GNSS Navigation Message + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNKNOWN, TYPE_GPS_L1CA, TYPE_GPS_L2CNAV, TYPE_GPS_L5CNAV, TYPE_GPS_CNAV2, + TYPE_GLO_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_GAL_I, TYPE_GAL_F}) + public @interface GnssNavigationMessageType {} + + // The following enumerations must be in sync with the values declared in gps.h + + /** Message type unknown */ + public static final int TYPE_UNKNOWN = 0; + /** GPS L1 C/A message contained in the structure. */ + public static final int TYPE_GPS_L1CA = 0x0101; + /** GPS L2-CNAV message contained in the structure. */ + public static final int TYPE_GPS_L2CNAV = 0x0102; + /** GPS L5-CNAV message contained in the structure. */ + public static final int TYPE_GPS_L5CNAV = 0x0103; + /** GPS CNAV-2 message contained in the structure. */ + public static final int TYPE_GPS_CNAV2 = 0x0104; + /** Glonass L1 CA message contained in the structure. */ + public static final int TYPE_GLO_L1CA = 0x0301; + /** Beidou D1 message contained in the structure. */ + public static final int TYPE_BDS_D1 = 0x0501; + /** Beidou D2 message contained in the structure. */ + public static final int TYPE_BDS_D2 = 0x0502; + /** Galileo I/NAV message contained in the structure. */ + public static final int TYPE_GAL_I = 0x0601; + /** Galileo F/NAV message contained in the structure. */ + public static final int TYPE_GAL_F = 0x0602; + + /** + * The Navigation Message Status is 'unknown'. + */ + public static final int STATUS_UNKNOWN = 0; + + /** + * The Navigation Message was received without any parity error in its navigation words. + */ + public static final int STATUS_PARITY_PASSED = (1<<0); + + /** + * The Navigation Message was received with words that failed parity check, but the receiver was + * able to correct those words. + */ + public static final int STATUS_PARITY_REBUILT = (1<<1); + + /** + * Used for receiving GNSS satellite Navigation Messages from the GNSS engine. + * + * <p>You can implement this interface and call + * {@link LocationManager#registerGnssNavigationMessageCallback}. + */ + public static abstract class Callback { + /** + * The status of GNSS Navigation Message event. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATUS_NOT_SUPPORTED, STATUS_READY, STATUS_LOCATION_DISABLED}) + public @interface GnssNavigationMessageStatus {} + + /** + * The system does not support tracking of GNSS Navigation Messages. + * + * This status will not change in the future. + */ + public static final int STATUS_NOT_SUPPORTED = 0; + + /** + * GNSS Navigation Messages are successfully being tracked, it will receive updates once + * they are available. + */ + public static final int STATUS_READY = 1; + + /** + * GNSS provider or Location is disabled, updated will not be received until they are + * enabled. + */ + public static final int STATUS_LOCATION_DISABLED = 2; + + /** + * Returns the latest collected GNSS Navigation Message. + */ + public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {} + + /** + * Returns the latest status of the GNSS Navigation Messages sub-system. + */ + public void onStatusChanged(@GnssNavigationMessageStatus int status) {} + } + + // End enumerations in sync with gps.h + + private int mType; + private int mSvid; + private int mMessageId; + private int mSubmessageId; + private byte[] mData; + private int mStatus; + + /** + * @hide + */ + @TestApi + public GnssNavigationMessage() { + initialize(); + } + + /** + * Sets all contents to the values stored in the provided object. + * @hide + */ + @TestApi + public void set(GnssNavigationMessage navigationMessage) { + mType = navigationMessage.mType; + mSvid = navigationMessage.mSvid; + mMessageId = navigationMessage.mMessageId; + mSubmessageId = navigationMessage.mSubmessageId; + mData = navigationMessage.mData; + mStatus = navigationMessage.mStatus; + } + + /** + * Resets all the contents to its original state. + * @hide + */ + @TestApi + public void reset() { + initialize(); + } + + /** + * Gets the type of the navigation message contained in the object. + */ + @GnssNavigationMessageType + public int getType() { + return mType; + } + + /** + * Sets the type of the navigation message. + * @hide + */ + @TestApi + public void setType(@GnssNavigationMessageType int value) { + mType = value; + } + + /** + * Gets a string representation of the 'type'. + * For internal and logging use only. + */ + private String getTypeString() { + switch (mType) { + case TYPE_UNKNOWN: + return "Unknown"; + case TYPE_GPS_L1CA: + return "GPS L1 C/A"; + case TYPE_GPS_L2CNAV: + return "GPS L2-CNAV"; + case TYPE_GPS_L5CNAV: + return "GPS L5-CNAV"; + case TYPE_GPS_CNAV2: + return "GPS CNAV2"; + case TYPE_GLO_L1CA: + return "Glonass L1 C/A"; + case TYPE_BDS_D1: + return "Beidou D1"; + case TYPE_BDS_D2: + return "Beidou D2"; + case TYPE_GAL_I: + return "Galileo I"; + case TYPE_GAL_F: + return "Galileo F"; + default: + return "<Invalid:" + mType + ">"; + } + } + + /** + * Gets the satellite ID. + * + * <p>Range varies by constellation. See definition at {@code GnssStatus#getSvid(int)} + */ + public int getSvid() { + return mSvid; + } + + /** + * Sets the satellite ID. + * @hide + */ + @TestApi + public void setSvid(int value) { + mSvid = value; + } + + /** + * Gets the Message identifier. + * + * <p>This provides an index to help with complete Navigation Message assembly. Similar + * identifiers within the data bits themselves often supplement this information, in ways even + * more specific to each message type; see the relevant satellite constellation ICDs for + * details. + * + * <ul> + * <li> For GPS L1 C/A subframe 4 and 5, this value corresponds to the 'frame id' of the + * navigation message, in the range of 1-25 (Subframe 1, 2, 3 does not contain a 'frame id' and + * this value can be set to -1.)</li> + * <li> For Glonass L1 C/A, this refers to the frame ID, in the range of 1-5.</li> + * <li> For BeiDou D1, this refers to the frame number in the range of 1-24</li> + * <li> For Beidou D2, this refers to the frame number, in the range of 1-120</li> + * <li> For Galileo F/NAV nominal frame structure, this refers to the subframe number, in the + * range of 1-12</li> + * <li> For Galileo I/NAV nominal frame structure, this refers to the subframe number in the + * range of 1-24</li> + * </ul> + */ + public int getMessageId() { + return mMessageId; + } + + /** + * Sets the Message Identifier. + * @hide + */ + @TestApi + public void setMessageId(int value) { + mMessageId = value; + } + + /** + * Gets the sub-message identifier, relevant to the {@link #getType()} of the message. + * + * <ul> + * <li> For GPS L1 C/A, BeiDou D1 & BeiDou D2, the submessage id corresponds to the subframe + * number of the navigation message, in the range of 1-5.</li> + * <li>For Glonass L1 C/A, this refers to the String number, in the range from 1-15</li> + * <li>For Galileo F/NAV, this refers to the page type in the range 1-6</li> + * <li>For Galileo I/NAV, this refers to the word type in the range 1-10+</li> + * <li>For Galileo in particular, the type information embedded within the data bits may be even + * more useful in interpretation, than the nominal page and word types provided in this + * field.</li> + * </ul> + */ + public int getSubmessageId() { + return mSubmessageId; + } + + /** + * Sets the Sub-message identifier. + * @hide + */ + @TestApi + public void setSubmessageId(int value) { + mSubmessageId = value; + } + + /** + * Gets the data of the reported GPS message. + * + * <p>The bytes (or words) specified using big endian format (MSB first). + * + * <ul> + * <li>For GPS L1 C/A, Beidou D1 & Beidou D2, each subframe contains 10 30-bit words. Each + * word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip B31 and B32), with + * MSB first, for a total of 40 bytes, covering a time period of 6, 6, and 0.6 seconds, + * respectively.</li> + * <li>For Glonass L1 C/A, each string contains 85 data bits, including the checksum. These + * bits should be fit into 11 bytes, with MSB first (skip B86-B88), covering a time period of 2 + * seconds.</li> + * <li>For Galileo F/NAV, each word consists of 238-bit (sync & tail symbols excluded). Each + * word should be fit into 30-bytes, with MSB first (skip B239, B240), covering a time period of + * 10 seconds.</li> + * <li>For Galileo I/NAV, each page contains 2 page parts, even and odd, with a total of 2x114 = + * 228 bits, (sync & tail excluded) that should be fit into 29 bytes, with MSB first (skip + * B229-B232).</li> + * </ul> + */ + @NonNull + public byte[] getData() { + return mData; + } + + /** + * Sets the data associated with the Navigation Message. + * @hide + */ + @TestApi + public void setData(byte[] value) { + if (value == null) { + throw new InvalidParameterException("Data must be a non-null array"); + } + + mData = value; + } + + /** + * Gets the Status of the navigation message contained in the object. + */ + public int getStatus() { + return mStatus; + } + + /** + * Sets the status of the navigation message. + * @hide + */ + @TestApi + public void setStatus(int value) { + mStatus = value; + } + + /** + * Gets a string representation of the 'status'. + * For internal and logging use only. + */ + private String getStatusString() { + switch (mStatus) { + case STATUS_UNKNOWN: + return "Unknown"; + case STATUS_PARITY_PASSED: + return "ParityPassed"; + case STATUS_PARITY_REBUILT: + return "ParityRebuilt"; + default: + return "<Invalid:" + mStatus + ">"; + } + } + + public static final Creator<GnssNavigationMessage> CREATOR = + new Creator<GnssNavigationMessage>() { + @Override + public GnssNavigationMessage createFromParcel(Parcel parcel) { + GnssNavigationMessage navigationMessage = new GnssNavigationMessage(); + + navigationMessage.setType(parcel.readInt()); + navigationMessage.setSvid(parcel.readInt()); + navigationMessage.setMessageId(parcel.readInt()); + navigationMessage.setSubmessageId(parcel.readInt()); + int dataLength = parcel.readInt(); + byte[] data = new byte[dataLength]; + parcel.readByteArray(data); + navigationMessage.setData(data); + navigationMessage.setStatus(parcel.readInt()); + + return navigationMessage; + } + + @Override + public GnssNavigationMessage[] newArray(int size) { + return new GnssNavigationMessage[size]; + } + }; + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mType); + parcel.writeInt(mSvid); + parcel.writeInt(mMessageId); + parcel.writeInt(mSubmessageId); + parcel.writeInt(mData.length); + parcel.writeByteArray(mData); + parcel.writeInt(mStatus); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + final String format = " %-15s = %s\n"; + StringBuilder builder = new StringBuilder("GnssNavigationMessage:\n"); + + builder.append(String.format(format, "Type", getTypeString())); + builder.append(String.format(format, "Svid", mSvid)); + builder.append(String.format(format, "Status", getStatusString())); + builder.append(String.format(format, "MessageId", mMessageId)); + builder.append(String.format(format, "SubmessageId", mSubmessageId)); + + builder.append(String.format(format, "Data", "{")); + String prefix = " "; + for(byte value : mData) { + builder.append(prefix); + builder.append(value); + prefix = ", "; + } + builder.append(" }"); + + return builder.toString(); + } + + private void initialize() { + mType = TYPE_UNKNOWN; + mSvid = 0; + mMessageId = -1; + mSubmessageId = -1; + mData = EMPTY_ARRAY; + mStatus = STATUS_UNKNOWN; + } +} diff --git a/android/location/GnssNavigationMessageCallbackTransport.java b/android/location/GnssNavigationMessageCallbackTransport.java new file mode 100644 index 00000000..1eafd02e --- /dev/null +++ b/android/location/GnssNavigationMessageCallbackTransport.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014 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.content.Context; +import android.os.RemoteException; + +/** + * A handler class to manage transport callback for {@link GnssNavigationMessage.Callback}. + * + * @hide + */ +class GnssNavigationMessageCallbackTransport + extends LocalListenerHelper<GnssNavigationMessage.Callback> { + private final ILocationManager mLocationManager; + + private final IGnssNavigationMessageListener mListenerTransport = new ListenerTransport(); + + public GnssNavigationMessageCallbackTransport( + Context context, + ILocationManager locationManager) { + super(context, "GnssNavigationMessageCallbackTransport"); + mLocationManager = locationManager; + } + + @Override + protected boolean registerWithServer() throws RemoteException { + return mLocationManager.addGnssNavigationMessageListener( + mListenerTransport, + getContext().getPackageName()); + } + + @Override + protected void unregisterFromServer() throws RemoteException { + mLocationManager.removeGnssNavigationMessageListener(mListenerTransport); + } + + private class ListenerTransport extends IGnssNavigationMessageListener.Stub { + @Override + public void onGnssNavigationMessageReceived(final GnssNavigationMessage event) { + ListenerOperation<GnssNavigationMessage.Callback> operation = + new ListenerOperation<GnssNavigationMessage.Callback>() { + @Override + public void execute(GnssNavigationMessage.Callback callback) + throws RemoteException { + callback.onGnssNavigationMessageReceived(event); + } + }; + foreach(operation); + } + + @Override + public void onStatusChanged(final int status) { + ListenerOperation<GnssNavigationMessage.Callback> operation = + new ListenerOperation<GnssNavigationMessage.Callback>() { + @Override + public void execute(GnssNavigationMessage.Callback callback) + throws RemoteException { + callback.onStatusChanged(status); + } + }; + foreach(operation); + } + } +} diff --git a/android/location/GnssStatus.java b/android/location/GnssStatus.java new file mode 100644 index 00000000..b2903c46 --- /dev/null +++ b/android/location/GnssStatus.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2016 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.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class represents the current state of the GNSS engine. + * This class is used in conjunction with the {@link GnssStatus.Callback}. + */ +public final class GnssStatus { + // these must match the definitions in gps.h + + /** Unknown constellation type. */ + public static final int CONSTELLATION_UNKNOWN = 0; + /** Constellation type constant for GPS. */ + public static final int CONSTELLATION_GPS = 1; + /** Constellation type constant for SBAS. */ + public static final int CONSTELLATION_SBAS = 2; + /** Constellation type constant for Glonass. */ + public static final int CONSTELLATION_GLONASS = 3; + /** Constellation type constant for QZSS. */ + public static final int CONSTELLATION_QZSS = 4; + /** Constellation type constant for Beidou. */ + public static final int CONSTELLATION_BEIDOU = 5; + /** Constellation type constant for Galileo. */ + public static final int CONSTELLATION_GALILEO = 6; + + /** @hide */ + public static final int GNSS_SV_FLAGS_NONE = 0; + /** @hide */ + public static final int GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA = (1 << 0); + /** @hide */ + public static final int GNSS_SV_FLAGS_HAS_ALMANAC_DATA = (1 << 1); + /** @hide */ + public static final int GNSS_SV_FLAGS_USED_IN_FIX = (1 << 2); + /** @hide */ + public static final int GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY = (1 << 3); + + /** @hide */ + public static final int SVID_SHIFT_WIDTH = 8; + /** @hide */ + public static final int CONSTELLATION_TYPE_SHIFT_WIDTH = 4; + /** @hide */ + public static final int CONSTELLATION_TYPE_MASK = 0xf; + + /** + * Used for receiving notifications when GNSS events happen. + */ + public static abstract class Callback { + /** + * Called when GNSS system has started. + */ + public void onStarted() {} + + /** + * Called when GNSS system has stopped. + */ + public void onStopped() {} + + /** + * Called when the GNSS system has received its first fix since starting. + * @param ttffMillis the time from start to first fix in milliseconds. + */ + public void onFirstFix(int ttffMillis) {} + + /** + * Called periodically to report GNSS satellite status. + * @param status the current status of all satellites. + */ + public void onSatelliteStatusChanged(GnssStatus status) {} + } + + /** + * Constellation type. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CONSTELLATION_UNKNOWN, CONSTELLATION_GPS, CONSTELLATION_SBAS, CONSTELLATION_GLONASS, + CONSTELLATION_QZSS, CONSTELLATION_BEIDOU, CONSTELLATION_GALILEO}) + public @interface ConstellationType {} + + final int[] mSvidWithFlags; + final float[] mCn0DbHz; + final float[] mElevations; + final float[] mAzimuths; + final int mSvCount; + final float[] mCarrierFrequencies; + + GnssStatus(int svCount, int[] svidWithFlags, float[] cn0s, float[] elevations, + float[] azimuths, float[] carrierFrequencies) { + mSvCount = svCount; + mSvidWithFlags = svidWithFlags; + mCn0DbHz = cn0s; + mElevations = elevations; + mAzimuths = azimuths; + mCarrierFrequencies = carrierFrequencies; + } + + /** + * Gets the total number of satellites in satellite list. + */ + public int getSatelliteCount() { + return mSvCount; + } + + /** + * Retrieves the constellation type of the satellite at the specified index. + * + * @param satIndex the index of the satellite in the list. + */ + @ConstellationType + public int getConstellationType(int satIndex) { + return ((mSvidWithFlags[satIndex] >> CONSTELLATION_TYPE_SHIFT_WIDTH) + & CONSTELLATION_TYPE_MASK); + } + + /** + * Gets the identification number for the satellite at the specific index. + * + * <p>This svid is pseudo-random number for most constellations. It is FCN & OSN number for + * Glonass. + * + * <p>The distinction is made by looking at constellation field + * {@link #getConstellationType(int)} Expected values are in the range of: + * + * <ul> + * <li>GPS: 1-32</li> + * <li>SBAS: 120-151, 183-192</li> + * <li>GLONASS: One of: OSN, or FCN+100 + * <ul> + * <li>1-24 as the orbital slot number (OSN) (preferred, if known)</li> + * <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100. + * i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li> + * </ul></li> + * <li>QZSS: 193-200</li> + * <li>Galileo: 1-36</li> + * <li>Beidou: 1-37</li> + * </ul> + * + * @param satIndex the index of the satellite in the list. + */ + public int getSvid(int satIndex) { + return mSvidWithFlags[satIndex] >> SVID_SHIFT_WIDTH; + } + + /** + * Retrieves the carrier-to-noise density at the antenna of the satellite at the specified index + * in dB-Hz. + * + * @param satIndex the index of the satellite in the list. + */ + public float getCn0DbHz(int satIndex) { + return mCn0DbHz[satIndex]; + } + + /** + * Retrieves the elevation of the satellite at the specified index. + * + * @param satIndex the index of the satellite in the list. + */ + public float getElevationDegrees(int satIndex) { + return mElevations[satIndex]; + } + + /** + * Retrieves the azimuth the satellite at the specified index. + * + * @param satIndex the index of the satellite in the list. + */ + public float getAzimuthDegrees(int satIndex) { + return mAzimuths[satIndex]; + } + + /** + * Reports whether the satellite at the specified index has ephemeris data. + * + * @param satIndex the index of the satellite in the list. + */ + public boolean hasEphemerisData(int satIndex) { + return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) != 0; + } + + /** + * Reports whether the satellite at the specified index has almanac data. + * + * @param satIndex the index of the satellite in the list. + */ + public boolean hasAlmanacData(int satIndex) { + return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_HAS_ALMANAC_DATA) != 0; + } + + /** + * Reports whether the satellite at the specified index was used in the calculation of the most + * recent position fix. + * + * @param satIndex the index of the satellite in the list. + */ + public boolean usedInFix(int satIndex) { + return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_USED_IN_FIX) != 0; + } + + /** + * Reports whether {@link #getCarrierFrequencyHz(int satIndex)} is available (i.e. carrier + * frequency is available for the satellite at the specified index). + * + * @param satIndex the index of the satellite in the list. + */ + public boolean hasCarrierFrequencyHz(int satIndex) { + return (mSvidWithFlags[satIndex] & GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY) != 0; + } + + /** + * Gets the carrier frequency of the signal tracked. + * + * <p>For example it can be the GPS central frequency for L1 = 1575.45 MHz, or L2 = 1227.60 MHz, + * L5 = 1176.45 MHz, varying GLO channels, etc. If the field is not set, it is the primary + * common use central frequency, e.g. L1 = 1575.45 MHz for GPS. + * + * For an L1, L5 receiver tracking a satellite on L1 and L5 at the same time, two measurements + * will be reported for this same satellite, in one all the values related to L1 will be filled, + * and in the other all of the values related to L5 will be filled. + * + * <p>The value is only available if {@link #hasCarrierFrequencyHz(int satIndex)} is {@code true}. + * + * @param satIndex the index of the satellite in the list. + * + * @return the carrier frequency of the signal tracked in Hz. + */ + public float getCarrierFrequencyHz(int satIndex) { + return mCarrierFrequencies[satIndex]; + } +} diff --git a/android/location/GpsClock.java b/android/location/GpsClock.java new file mode 100644 index 00000000..4135a1c9 --- /dev/null +++ b/android/location/GpsClock.java @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2014 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; + +/** + * A class containing a GPS clock timestamp. + * It represents a measurement of the GPS receiver's clock. + * + * @hide + */ +@SystemApi +public class GpsClock implements Parcelable { + + // The following enumerations must be in sync with the values declared in gps.h + + /** + * The type of the time stored is not available or it is unknown. + */ + public static final byte TYPE_UNKNOWN = 0; + + /** + * The source of the time value reported by this class is the 'Local Hardware Clock'. + */ + public static final byte TYPE_LOCAL_HW_TIME = 1; + + /** + * The source of the time value reported by this class is the 'GPS time' derived from + * satellites (epoch = Jan 6, 1980). + */ + public static final byte TYPE_GPS_TIME = 2; + + private static final short HAS_NO_FLAGS = 0; + private static final short HAS_LEAP_SECOND = (1<<0); + private static final short HAS_TIME_UNCERTAINTY = (1<<1); + private static final short HAS_FULL_BIAS = (1<<2); + private static final short HAS_BIAS = (1<<3); + private static final short HAS_BIAS_UNCERTAINTY = (1<<4); + private static final short HAS_DRIFT = (1<<5); + private static final short HAS_DRIFT_UNCERTAINTY = (1<<6); + + // End enumerations in sync with gps.h + + private short mFlags; + private short mLeapSecond; + private byte mType; + private long mTimeInNs; + private double mTimeUncertaintyInNs; + private long mFullBiasInNs; + private double mBiasInNs; + private double mBiasUncertaintyInNs; + private double mDriftInNsPerSec; + private double mDriftUncertaintyInNsPerSec; + + GpsClock() { + initialize(); + } + + /** + * Sets all contents to the values stored in the provided object. + */ + public void set(GpsClock clock) { + mFlags = clock.mFlags; + mLeapSecond = clock.mLeapSecond; + mType = clock.mType; + mTimeInNs = clock.mTimeInNs; + mTimeUncertaintyInNs = clock.mTimeUncertaintyInNs; + mFullBiasInNs = clock.mFullBiasInNs; + mBiasInNs = clock.mBiasInNs; + mBiasUncertaintyInNs = clock.mBiasUncertaintyInNs; + mDriftInNsPerSec = clock.mDriftInNsPerSec; + mDriftUncertaintyInNsPerSec = clock.mDriftUncertaintyInNsPerSec; + } + + /** + * Resets all the contents to its original state. + */ + public void reset() { + initialize(); + } + + /** + * Gets the type of time reported by {@link #getTimeInNs()}. + */ + public byte getType() { + return mType; + } + + /** + * Sets the type of time reported. + */ + public void setType(byte value) { + mType = value; + } + + /** + * Gets a string representation of the 'type'. + * For internal and logging use only. + */ + private String getTypeString() { + switch (mType) { + case TYPE_UNKNOWN: + return "Unknown"; + case TYPE_GPS_TIME: + return "GpsTime"; + case TYPE_LOCAL_HW_TIME: + return "LocalHwClock"; + default: + return "<Invalid:" + mType + ">"; + } + } + + /** + * Returns true if {@link #getLeapSecond()} is available, false otherwise. + */ + public boolean hasLeapSecond() { + return isFlagSet(HAS_LEAP_SECOND); + } + + /** + * Gets the leap second associated with the clock's time. + * The sign of the value is defined by the following equation: + * utc_time_ns = time_ns + (full_bias_ns + bias_ns) - leap_second * 1,000,000,000 + * + * The value is only available if {@link #hasLeapSecond()} is true. + */ + public short getLeapSecond() { + return mLeapSecond; + } + + /** + * Sets the leap second associated with the clock's time. + */ + public void setLeapSecond(short leapSecond) { + setFlag(HAS_LEAP_SECOND); + mLeapSecond = leapSecond; + } + + /** + * Resets the leap second associated with the clock's time. + */ + public void resetLeapSecond() { + resetFlag(HAS_LEAP_SECOND); + mLeapSecond = Short.MIN_VALUE; + } + + /** + * Gets the GPS receiver internal clock value in nanoseconds. + * This can be either the 'local hardware clock' value ({@link #TYPE_LOCAL_HW_TIME}), or the + * current GPS time derived inside GPS receiver ({@link #TYPE_GPS_TIME}). + * {@link #getType()} defines the time reported. + * + * For 'local hardware clock' this value is expected to be monotonically increasing during the + * reporting session. The real GPS time can be derived by compensating + * {@link #getFullBiasInNs()} (when it is available) from this value. + * + * For 'GPS time' this value is expected to be the best estimation of current GPS time that GPS + * receiver can achieve. {@link #getTimeUncertaintyInNs()} should be available when GPS time is + * specified. + * + * Sub-nanosecond accuracy can be provided by means of {@link #getBiasInNs()}. + * The reported time includes {@link #getTimeUncertaintyInNs()}. + */ + public long getTimeInNs() { + return mTimeInNs; + } + + /** + * Sets the GPS receiver internal clock in nanoseconds. + */ + public void setTimeInNs(long timeInNs) { + mTimeInNs = timeInNs; + } + + /** + * Returns true if {@link #getTimeUncertaintyInNs()} is available, false otherwise. + */ + public boolean hasTimeUncertaintyInNs() { + return isFlagSet(HAS_TIME_UNCERTAINTY); + } + + /** + * Gets the clock's time Uncertainty (1-Sigma) in nanoseconds. + * The uncertainty is represented as an absolute (single sided) value. + * + * The value is only available if {@link #hasTimeUncertaintyInNs()} is true. + */ + public double getTimeUncertaintyInNs() { + return mTimeUncertaintyInNs; + } + + /** + * Sets the clock's Time Uncertainty (1-Sigma) in nanoseconds. + */ + public void setTimeUncertaintyInNs(double timeUncertaintyInNs) { + setFlag(HAS_TIME_UNCERTAINTY); + mTimeUncertaintyInNs = timeUncertaintyInNs; + } + + /** + * Resets the clock's Time Uncertainty (1-Sigma) in nanoseconds. + */ + public void resetTimeUncertaintyInNs() { + resetFlag(HAS_TIME_UNCERTAINTY); + mTimeUncertaintyInNs = Double.NaN; + } + + /** + * Returns true if {@link @getFullBiasInNs()} is available, false otherwise. + */ + public boolean hasFullBiasInNs() { + return isFlagSet(HAS_FULL_BIAS); + } + + /** + * Gets the difference between hardware clock ({@link #getTimeInNs()}) inside GPS receiver and + * the true GPS time since 0000Z, January 6, 1980, in nanoseconds. + * + * This value is available if {@link #TYPE_LOCAL_HW_TIME} is set, and GPS receiver has solved + * the clock for GPS time. + * {@link #getBiasUncertaintyInNs()} should be used for quality check. + * + * The sign of the value is defined by the following equation: + * true time (GPS time) = time_ns + (full_bias_ns + bias_ns) + * + * The reported full bias includes {@link #getBiasUncertaintyInNs()}. + * The value is onl available if {@link #hasFullBiasInNs()} is true. + */ + public long getFullBiasInNs() { + return mFullBiasInNs; + } + + /** + * Sets the full bias in nanoseconds. + */ + public void setFullBiasInNs(long value) { + setFlag(HAS_FULL_BIAS); + mFullBiasInNs = value; + } + + /** + * Resets the full bias in nanoseconds. + */ + public void resetFullBiasInNs() { + resetFlag(HAS_FULL_BIAS); + mFullBiasInNs = Long.MIN_VALUE; + } + + /** + * Returns true if {@link #getBiasInNs()} is available, false otherwise. + */ + public boolean hasBiasInNs() { + return isFlagSet(HAS_BIAS); + } + + /** + * Gets the clock's sub-nanosecond bias. + * The reported bias includes {@link #getBiasUncertaintyInNs()}. + * + * The value is only available if {@link #hasBiasInNs()} is true. + */ + public double getBiasInNs() { + return mBiasInNs; + } + + /** + * Sets the sub-nanosecond bias. + */ + public void setBiasInNs(double biasInNs) { + setFlag(HAS_BIAS); + mBiasInNs = biasInNs; + } + + /** + * Resets the clock's Bias in nanoseconds. + */ + public void resetBiasInNs() { + resetFlag(HAS_BIAS); + mBiasInNs = Double.NaN; + } + + /** + * Returns true if {@link #getBiasUncertaintyInNs()} is available, false otherwise. + */ + public boolean hasBiasUncertaintyInNs() { + return isFlagSet(HAS_BIAS_UNCERTAINTY); + } + + /** + * Gets the clock's Bias Uncertainty (1-Sigma) in nanoseconds. + * + * The value is only available if {@link #hasBiasUncertaintyInNs()} is true. + */ + public double getBiasUncertaintyInNs() { + return mBiasUncertaintyInNs; + } + + /** + * Sets the clock's Bias Uncertainty (1-Sigma) in nanoseconds. + */ + public void setBiasUncertaintyInNs(double biasUncertaintyInNs) { + setFlag(HAS_BIAS_UNCERTAINTY); + mBiasUncertaintyInNs = biasUncertaintyInNs; + } + + /** + * Resets the clock's Bias Uncertainty (1-Sigma) in nanoseconds. + */ + public void resetBiasUncertaintyInNs() { + resetFlag(HAS_BIAS_UNCERTAINTY); + mBiasUncertaintyInNs = Double.NaN; + } + + /** + * Returns true if {@link #getDriftInNsPerSec()} is available, false otherwise. + */ + public boolean hasDriftInNsPerSec() { + return isFlagSet(HAS_DRIFT); + } + + /** + * Gets the clock's Drift in nanoseconds per second. + * A positive value indicates that the frequency is higher than the nominal frequency. + * The reported drift includes {@link #getDriftUncertaintyInNsPerSec()}. + * + * The value is only available if {@link #hasDriftInNsPerSec()} is true. + */ + public double getDriftInNsPerSec() { + return mDriftInNsPerSec; + } + + /** + * Sets the clock's Drift in nanoseconds per second. + */ + public void setDriftInNsPerSec(double driftInNsPerSec) { + setFlag(HAS_DRIFT); + mDriftInNsPerSec = driftInNsPerSec; + } + + /** + * Resets the clock's Drift in nanoseconds per second. + */ + public void resetDriftInNsPerSec() { + resetFlag(HAS_DRIFT); + mDriftInNsPerSec = Double.NaN; + } + + /** + * Returns true if {@link #getDriftUncertaintyInNsPerSec()} is available, false otherwise. + */ + public boolean hasDriftUncertaintyInNsPerSec() { + return isFlagSet(HAS_DRIFT_UNCERTAINTY); + } + + /** + * Gets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second. + * + * The value is only available if {@link #hasDriftUncertaintyInNsPerSec()} is true. + */ + public double getDriftUncertaintyInNsPerSec() { + return mDriftUncertaintyInNsPerSec; + } + + /** + * Sets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second. + */ + public void setDriftUncertaintyInNsPerSec(double driftUncertaintyInNsPerSec) { + setFlag(HAS_DRIFT_UNCERTAINTY); + mDriftUncertaintyInNsPerSec = driftUncertaintyInNsPerSec; + } + + /** + * Resets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second. + */ + public void resetDriftUncertaintyInNsPerSec() { + resetFlag(HAS_DRIFT_UNCERTAINTY); + mDriftUncertaintyInNsPerSec = Double.NaN; + } + + public static final Creator<GpsClock> CREATOR = new Creator<GpsClock>() { + @Override + public GpsClock createFromParcel(Parcel parcel) { + GpsClock gpsClock = new GpsClock(); + + gpsClock.mFlags = (short) parcel.readInt(); + gpsClock.mLeapSecond = (short) parcel.readInt(); + gpsClock.mType = parcel.readByte(); + gpsClock.mTimeInNs = parcel.readLong(); + gpsClock.mTimeUncertaintyInNs = parcel.readDouble(); + gpsClock.mFullBiasInNs = parcel.readLong(); + gpsClock.mBiasInNs = parcel.readDouble(); + gpsClock.mBiasUncertaintyInNs = parcel.readDouble(); + gpsClock.mDriftInNsPerSec = parcel.readDouble(); + gpsClock.mDriftUncertaintyInNsPerSec = parcel.readDouble(); + + return gpsClock; + } + + @Override + public GpsClock[] newArray(int size) { + return new GpsClock[size]; + } + }; + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mFlags); + parcel.writeInt(mLeapSecond); + parcel.writeByte(mType); + parcel.writeLong(mTimeInNs); + parcel.writeDouble(mTimeUncertaintyInNs); + parcel.writeLong(mFullBiasInNs); + parcel.writeDouble(mBiasInNs); + parcel.writeDouble(mBiasUncertaintyInNs); + parcel.writeDouble(mDriftInNsPerSec); + parcel.writeDouble(mDriftUncertaintyInNsPerSec); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + final String format = " %-15s = %s\n"; + final String formatWithUncertainty = " %-15s = %-25s %-26s = %s\n"; + StringBuilder builder = new StringBuilder("GpsClock:\n"); + + builder.append(String.format(format, "Type", getTypeString())); + + builder.append(String.format(format, "LeapSecond", hasLeapSecond() ? mLeapSecond : null)); + + builder.append(String.format( + formatWithUncertainty, + "TimeInNs", + mTimeInNs, + "TimeUncertaintyInNs", + hasTimeUncertaintyInNs() ? mTimeUncertaintyInNs : null)); + + builder.append(String.format( + format, + "FullBiasInNs", + hasFullBiasInNs() ? mFullBiasInNs : null)); + + builder.append(String.format( + formatWithUncertainty, + "BiasInNs", + hasBiasInNs() ? mBiasInNs : null, + "BiasUncertaintyInNs", + hasBiasUncertaintyInNs() ? mBiasUncertaintyInNs : null)); + + builder.append(String.format( + formatWithUncertainty, + "DriftInNsPerSec", + hasDriftInNsPerSec() ? mDriftInNsPerSec : null, + "DriftUncertaintyInNsPerSec", + hasDriftUncertaintyInNsPerSec() ? mDriftUncertaintyInNsPerSec : null)); + + return builder.toString(); + } + + private void initialize() { + mFlags = HAS_NO_FLAGS; + resetLeapSecond(); + setType(TYPE_UNKNOWN); + setTimeInNs(Long.MIN_VALUE); + resetTimeUncertaintyInNs(); + resetFullBiasInNs(); + resetBiasInNs(); + resetBiasUncertaintyInNs(); + resetDriftInNsPerSec(); + resetDriftUncertaintyInNsPerSec(); + } + + private void setFlag(short flag) { + mFlags |= flag; + } + + private void resetFlag(short flag) { + mFlags &= ~flag; + } + + private boolean isFlagSet(short flag) { + return (mFlags & flag) == flag; + } +} diff --git a/android/location/GpsMeasurement.java b/android/location/GpsMeasurement.java new file mode 100644 index 00000000..f13a440f --- /dev/null +++ b/android/location/GpsMeasurement.java @@ -0,0 +1,1413 @@ +/* + * Copyright (C) 2014 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; + +/** + * A class representing a GPS satellite measurement, containing raw and computed information. + * + * @hide + */ +@SystemApi +public class GpsMeasurement implements Parcelable { + private int mFlags; + private byte mPrn; + private double mTimeOffsetInNs; + private short mState; + private long mReceivedGpsTowInNs; + private long mReceivedGpsTowUncertaintyInNs; + private double mCn0InDbHz; + private double mPseudorangeRateInMetersPerSec; + private double mPseudorangeRateUncertaintyInMetersPerSec; + private short mAccumulatedDeltaRangeState; + private double mAccumulatedDeltaRangeInMeters; + private double mAccumulatedDeltaRangeUncertaintyInMeters; + private double mPseudorangeInMeters; + private double mPseudorangeUncertaintyInMeters; + private double mCodePhaseInChips; + private double mCodePhaseUncertaintyInChips; + private float mCarrierFrequencyInHz; + private long mCarrierCycles; + private double mCarrierPhase; + private double mCarrierPhaseUncertainty; + private byte mLossOfLock; + private int mBitNumber; + private short mTimeFromLastBitInMs; + private double mDopplerShiftInHz; + private double mDopplerShiftUncertaintyInHz; + private byte mMultipathIndicator; + private double mSnrInDb; + private double mElevationInDeg; + private double mElevationUncertaintyInDeg; + private double mAzimuthInDeg; + private double mAzimuthUncertaintyInDeg; + private boolean mUsedInFix; + + // The following enumerations must be in sync with the values declared in gps.h + + private static final int HAS_NO_FLAGS = 0; + private static final int HAS_SNR = (1<<0); + private static final int HAS_ELEVATION = (1<<1); + private static final int HAS_ELEVATION_UNCERTAINTY = (1<<2); + private static final int HAS_AZIMUTH = (1<<3); + private static final int HAS_AZIMUTH_UNCERTAINTY = (1<<4); + private static final int HAS_PSEUDORANGE = (1<<5); + private static final int HAS_PSEUDORANGE_UNCERTAINTY = (1<<6); + private static final int HAS_CODE_PHASE = (1<<7); + private static final int HAS_CODE_PHASE_UNCERTAINTY = (1<<8); + private static final int HAS_CARRIER_FREQUENCY = (1<<9); + private static final int HAS_CARRIER_CYCLES = (1<<10); + private static final int HAS_CARRIER_PHASE = (1<<11); + private static final int HAS_CARRIER_PHASE_UNCERTAINTY = (1<<12); + private static final int HAS_BIT_NUMBER = (1<<13); + private static final int HAS_TIME_FROM_LAST_BIT = (1<<14); + private static final int HAS_DOPPLER_SHIFT = (1<<15); + private static final int HAS_DOPPLER_SHIFT_UNCERTAINTY = (1<<16); + private static final int HAS_USED_IN_FIX = (1<<17); + private static final int GPS_MEASUREMENT_HAS_UNCORRECTED_PSEUDORANGE_RATE = (1<<18); + + /** + * The indicator is not available or it is unknown. + */ + public static final byte LOSS_OF_LOCK_UNKNOWN = 0; + + /** + * The measurement does not present any indication of 'loss of lock'. + */ + public static final byte LOSS_OF_LOCK_OK = 1; + + /** + * 'Loss of lock' detected between the previous and current observation: cycle slip possible. + */ + public static final byte LOSS_OF_LOCK_CYCLE_SLIP = 2; + + /** + * The indicator is not available or it is unknown. + */ + public static final byte MULTIPATH_INDICATOR_UNKNOWN = 0; + + /** + * The measurement has been indicated to use multi-path. + */ + public static final byte MULTIPATH_INDICATOR_DETECTED = 1; + + /** + * The measurement has been indicated not tu use multi-path. + */ + public static final byte MULTIPATH_INDICATOR_NOT_USED = 2; + + /** + * The state of GPS receiver the measurement is invalid or unknown. + */ + public static final short STATE_UNKNOWN = 0; + + /** + * The state of the GPS receiver is ranging code lock. + */ + public static final short STATE_CODE_LOCK = (1<<0); + + /** + * The state of the GPS receiver is in bit sync. + */ + public static final short STATE_BIT_SYNC = (1<<1); + + /** + *The state of the GPS receiver is in sub-frame sync. + */ + public static final short STATE_SUBFRAME_SYNC = (1<<2); + + /** + * The state of the GPS receiver has TOW decoded. + */ + public static final short STATE_TOW_DECODED = (1<<3); + + /** + * The state of the GPS receiver contains millisecond ambiguity. + */ + public static final short STATE_MSEC_AMBIGUOUS = (1<<4); + + /** + * All the GPS receiver state flags. + */ + private static final short STATE_ALL = STATE_CODE_LOCK | STATE_BIT_SYNC | STATE_SUBFRAME_SYNC + | STATE_TOW_DECODED | STATE_MSEC_AMBIGUOUS; + + /** + * The state of the 'Accumulated Delta Range' is invalid or unknown. + */ + public static final short ADR_STATE_UNKNOWN = 0; + + /** + * The state of the 'Accumulated Delta Range' is valid. + */ + public static final short ADR_STATE_VALID = (1<<0); + + /** + * The state of the 'Accumulated Delta Range' has detected a reset. + */ + public static final short ADR_STATE_RESET = (1<<1); + + /** + * The state of the 'Accumulated Delta Range' has a cycle slip detected. + */ + public static final short ADR_STATE_CYCLE_SLIP = (1<<2); + + /** + * All the 'Accumulated Delta Range' flags. + */ + private static final short ADR_ALL = ADR_STATE_VALID | ADR_STATE_RESET | ADR_STATE_CYCLE_SLIP; + + // End enumerations in sync with gps.h + + GpsMeasurement() { + initialize(); + } + + /** + * Sets all contents to the values stored in the provided object. + */ + public void set(GpsMeasurement measurement) { + mFlags = measurement.mFlags; + mPrn = measurement.mPrn; + mTimeOffsetInNs = measurement.mTimeOffsetInNs; + mState = measurement.mState; + mReceivedGpsTowInNs = measurement.mReceivedGpsTowInNs; + mReceivedGpsTowUncertaintyInNs = measurement.mReceivedGpsTowUncertaintyInNs; + mCn0InDbHz = measurement.mCn0InDbHz; + mPseudorangeRateInMetersPerSec = measurement.mPseudorangeRateInMetersPerSec; + mPseudorangeRateUncertaintyInMetersPerSec = + measurement.mPseudorangeRateUncertaintyInMetersPerSec; + mAccumulatedDeltaRangeState = measurement.mAccumulatedDeltaRangeState; + mAccumulatedDeltaRangeInMeters = measurement.mAccumulatedDeltaRangeInMeters; + mAccumulatedDeltaRangeUncertaintyInMeters = + measurement.mAccumulatedDeltaRangeUncertaintyInMeters; + mPseudorangeInMeters = measurement.mPseudorangeInMeters; + mPseudorangeUncertaintyInMeters = measurement.mPseudorangeUncertaintyInMeters; + mCodePhaseInChips = measurement.mCodePhaseInChips; + mCodePhaseUncertaintyInChips = measurement.mCodePhaseUncertaintyInChips; + mCarrierFrequencyInHz = measurement.mCarrierFrequencyInHz; + mCarrierCycles = measurement.mCarrierCycles; + mCarrierPhase = measurement.mCarrierPhase; + mCarrierPhaseUncertainty = measurement.mCarrierPhaseUncertainty; + mLossOfLock = measurement.mLossOfLock; + mBitNumber = measurement.mBitNumber; + mTimeFromLastBitInMs = measurement.mTimeFromLastBitInMs; + mDopplerShiftInHz = measurement.mDopplerShiftInHz; + mDopplerShiftUncertaintyInHz = measurement.mDopplerShiftUncertaintyInHz; + mMultipathIndicator = measurement.mMultipathIndicator; + mSnrInDb = measurement.mSnrInDb; + mElevationInDeg = measurement.mElevationInDeg; + mElevationUncertaintyInDeg = measurement.mElevationUncertaintyInDeg; + mAzimuthInDeg = measurement.mAzimuthInDeg; + mAzimuthUncertaintyInDeg = measurement.mAzimuthUncertaintyInDeg; + mUsedInFix = measurement.mUsedInFix; + } + + /** + * Resets all the contents to its original state. + */ + public void reset() { + initialize(); + } + + /** + * Gets the Pseudo-random number (PRN). + * Range: [1, 32] + */ + public byte getPrn() { + return mPrn; + } + + /** + * Sets the Pseud-random number (PRN). + */ + public void setPrn(byte value) { + mPrn = value; + } + + /** + * Gets the time offset at which the measurement was taken in nanoseconds. + * The reference receiver's time is specified by {@link GpsClock#getTimeInNs()} and should be + * interpreted in the same way as indicated by {@link GpsClock#getType()}. + * + * The sign of this value is given by the following equation: + * measurement time = time_ns + time_offset_ns + * + * The value provides an individual time-stamp for the measurement, and allows sub-nanosecond + * accuracy. + */ + public double getTimeOffsetInNs() { + return mTimeOffsetInNs; + } + + /** + * Sets the time offset at which the measurement was taken in nanoseconds. + */ + public void setTimeOffsetInNs(double value) { + mTimeOffsetInNs = value; + } + + /** + * Gets per-satellite sync state. + * It represents the current sync state for the associated satellite. + * + * This value helps interpret {@link #getReceivedGpsTowInNs()}. + */ + public short getState() { + return mState; + } + + /** + * Sets the sync state. + */ + public void setState(short value) { + mState = value; + } + + /** + * Gets a string representation of the 'sync state'. + * For internal and logging use only. + */ + private String getStateString() { + if (mState == STATE_UNKNOWN) { + return "Unknown"; + } + StringBuilder builder = new StringBuilder(); + if ((mState & STATE_CODE_LOCK) == STATE_CODE_LOCK) { + builder.append("CodeLock|"); + } + if ((mState & STATE_BIT_SYNC) == STATE_BIT_SYNC) { + builder.append("BitSync|"); + } + if ((mState & STATE_SUBFRAME_SYNC) == STATE_SUBFRAME_SYNC) { + builder.append("SubframeSync|"); + } + if ((mState & STATE_TOW_DECODED) == STATE_TOW_DECODED) { + builder.append("TowDecoded|"); + } + if ((mState & STATE_MSEC_AMBIGUOUS) == STATE_MSEC_AMBIGUOUS) { + builder.append("MsecAmbiguous"); + } + int remainingStates = mState & ~STATE_ALL; + if (remainingStates > 0) { + builder.append("Other("); + builder.append(Integer.toBinaryString(remainingStates)); + builder.append(")|"); + } + builder.deleteCharAt(builder.length() - 1); + return builder.toString(); + } + + /** + * Gets the received GPS Time-of-Week at the measurement time, in nanoseconds. + * The value is relative to the beginning of the current GPS week. + * + * Given {@link #getState()} of the GPS receiver, the range of this field can be: + * Searching : [ 0 ] : {@link #STATE_UNKNOWN} is set + * Ranging code lock : [ 0 1 ms ] : {@link #STATE_CODE_LOCK} is set + * Bit sync : [ 0 20 ms ] : {@link #STATE_BIT_SYNC} is set + * Subframe sync : [ 0 6 ms ] : {@link #STATE_SUBFRAME_SYNC} is set + * TOW decoded : [ 0 1 week ] : {@link #STATE_TOW_DECODED} is set + */ + public long getReceivedGpsTowInNs() { + return mReceivedGpsTowInNs; + } + + /** + * Sets the received GPS time-of-week in nanoseconds. + */ + public void setReceivedGpsTowInNs(long value) { + mReceivedGpsTowInNs = value; + } + + /** + * Gets the received GPS time-of-week's uncertainty (1-Sigma) in nanoseconds. + */ + public long getReceivedGpsTowUncertaintyInNs() { + return mReceivedGpsTowUncertaintyInNs; + } + + /** + * Sets the received GPS time-of-week's uncertainty (1-Sigma) in nanoseconds. + */ + public void setReceivedGpsTowUncertaintyInNs(long value) { + mReceivedGpsTowUncertaintyInNs = value; + } + + /** + * Gets the Carrier-to-noise density in dB-Hz. + * Range: [0, 63]. + * + * The value contains the measured C/N0 for the signal at the antenna input. + */ + public double getCn0InDbHz() { + return mCn0InDbHz; + } + + /** + * Sets the carrier-to-noise density in dB-Hz. + */ + public void setCn0InDbHz(double value) { + mCn0InDbHz = value; + } + + /** + * Gets the Pseudorange rate at the timestamp in m/s. + * The reported value includes {@link #getPseudorangeRateUncertaintyInMetersPerSec()}. + * + * The correction of a given Pseudorange Rate value includes corrections from receiver and + * satellite clock frequency errors. + * {@link #isPseudorangeRateCorrected()} identifies the type of value reported. + * + * A positive 'uncorrected' value indicates that the SV is moving away from the receiver. + * The sign of the 'uncorrected' Pseudorange Rate and its relation to the sign of + * {@link #getDopplerShiftInHz()} is given by the equation: + * pseudorange rate = -k * doppler shift (where k is a constant) + */ + public double getPseudorangeRateInMetersPerSec() { + return mPseudorangeRateInMetersPerSec; + } + + /** + * Sets the pseudorange rate at the timestamp in m/s. + */ + public void setPseudorangeRateInMetersPerSec(double value) { + mPseudorangeRateInMetersPerSec = value; + } + + /** + * See {@link #getPseudorangeRateInMetersPerSec()} for more details. + * + * @return {@code true} if {@link #getPseudorangeRateInMetersPerSec()} contains a corrected + * value, {@code false} if it contains an uncorrected value. + */ + public boolean isPseudorangeRateCorrected() { + return !isFlagSet(GPS_MEASUREMENT_HAS_UNCORRECTED_PSEUDORANGE_RATE); + } + + /** + * Gets the pseudorange's rate uncertainty (1-Sigma) in m/s. + * The uncertainty is represented as an absolute (single sided) value. + */ + public double getPseudorangeRateUncertaintyInMetersPerSec() { + return mPseudorangeRateUncertaintyInMetersPerSec; + } + + /** + * Sets the pseudorange's rate uncertainty (1-Sigma) in m/s. + */ + public void setPseudorangeRateUncertaintyInMetersPerSec(double value) { + mPseudorangeRateUncertaintyInMetersPerSec = value; + } + + /** + * Gets 'Accumulated Delta Range' state. + * It indicates whether {@link #getAccumulatedDeltaRangeInMeters()} is reset or there is a + * cycle slip (indicating 'loss of lock'). + */ + public short getAccumulatedDeltaRangeState() { + return mAccumulatedDeltaRangeState; + } + + /** + * Sets the 'Accumulated Delta Range' state. + */ + public void setAccumulatedDeltaRangeState(short value) { + mAccumulatedDeltaRangeState = value; + } + + /** + * Gets a string representation of the 'Accumulated Delta Range state'. + * For internal and logging use only. + */ + private String getAccumulatedDeltaRangeStateString() { + if (mAccumulatedDeltaRangeState == ADR_STATE_UNKNOWN) { + return "Unknown"; + } + StringBuilder builder = new StringBuilder(); + if ((mAccumulatedDeltaRangeState & ADR_STATE_VALID) == ADR_STATE_VALID) { + builder.append("Valid|"); + } + if ((mAccumulatedDeltaRangeState & ADR_STATE_RESET) == ADR_STATE_RESET) { + builder.append("Reset|"); + } + if ((mAccumulatedDeltaRangeState & ADR_STATE_CYCLE_SLIP) == ADR_STATE_CYCLE_SLIP) { + builder.append("CycleSlip|"); + } + int remainingStates = mAccumulatedDeltaRangeState & ~ADR_ALL; + if (remainingStates > 0) { + builder.append("Other("); + builder.append(Integer.toBinaryString(remainingStates)); + builder.append(")|"); + } + builder.deleteCharAt(builder.length() - 1); + return builder.toString(); + } + + /** + * Gets the accumulated delta range since the last channel reset, in meters. + * The reported value includes {@link #getAccumulatedDeltaRangeUncertaintyInMeters()}. + * + * The availability of the value is represented by {@link #getAccumulatedDeltaRangeState()}. + * + * A positive value indicates that the SV is moving away from the receiver. + * The sign of {@link #getAccumulatedDeltaRangeInMeters()} and its relation to the sign of + * {@link #getCarrierPhase()} is given by the equation: + * accumulated delta range = -k * carrier phase (where k is a constant) + */ + public double getAccumulatedDeltaRangeInMeters() { + return mAccumulatedDeltaRangeInMeters; + } + + /** + * Sets the accumulated delta range in meters. + */ + public void setAccumulatedDeltaRangeInMeters(double value) { + mAccumulatedDeltaRangeInMeters = value; + } + + /** + * Gets the accumulated delta range's uncertainty (1-Sigma) in meters. + * The uncertainty is represented as an absolute (single sided) value. + * + * The status of the value is represented by {@link #getAccumulatedDeltaRangeState()}. + */ + public double getAccumulatedDeltaRangeUncertaintyInMeters() { + return mAccumulatedDeltaRangeUncertaintyInMeters; + } + + /** + * Sets the accumulated delta range's uncertainty (1-sigma) in meters. + * + * The status of the value is represented by {@link #getAccumulatedDeltaRangeState()}. + */ + public void setAccumulatedDeltaRangeUncertaintyInMeters(double value) { + mAccumulatedDeltaRangeUncertaintyInMeters = value; + } + + /** + * Returns true if {@link #getPseudorangeInMeters()} is available, false otherwise. + */ + public boolean hasPseudorangeInMeters() { + return isFlagSet(HAS_PSEUDORANGE); + } + + /** + * Gets the best derived pseudorange by the chipset, in meters. + * The reported pseudorange includes {@link #getPseudorangeUncertaintyInMeters()}. + * + * The value is only available if {@link #hasPseudorangeInMeters()} is true. + */ + public double getPseudorangeInMeters() { + return mPseudorangeInMeters; + } + + /** + * Sets the Pseudo-range in meters. + */ + public void setPseudorangeInMeters(double value) { + setFlag(HAS_PSEUDORANGE); + mPseudorangeInMeters = value; + } + + /** + * Resets the Pseudo-range in meters. + */ + public void resetPseudorangeInMeters() { + resetFlag(HAS_PSEUDORANGE); + mPseudorangeInMeters = Double.NaN; + } + + /** + * Returns true if {@link #getPseudorangeUncertaintyInMeters()} is available, false otherwise. + */ + public boolean hasPseudorangeUncertaintyInMeters() { + return isFlagSet(HAS_PSEUDORANGE_UNCERTAINTY); + } + + /** + * Gets the pseudorange's uncertainty (1-Sigma) in meters. + * The value contains the 'pseudorange' and 'clock' uncertainty in it. + * The uncertainty is represented as an absolute (single sided) value. + * + * The value is only available if {@link #hasPseudorangeUncertaintyInMeters()} is true. + */ + public double getPseudorangeUncertaintyInMeters() { + return mPseudorangeUncertaintyInMeters; + } + + /** + * Sets the pseudo-range's uncertainty (1-Sigma) in meters. + */ + public void setPseudorangeUncertaintyInMeters(double value) { + setFlag(HAS_PSEUDORANGE_UNCERTAINTY); + mPseudorangeUncertaintyInMeters = value; + } + + /** + * Resets the pseudo-range's uncertainty (1-Sigma) in meters. + */ + public void resetPseudorangeUncertaintyInMeters() { + resetFlag(HAS_PSEUDORANGE_UNCERTAINTY); + mPseudorangeUncertaintyInMeters = Double.NaN; + } + + /** + * Returns true if {@link #getCodePhaseInChips()} is available, false otherwise. + */ + public boolean hasCodePhaseInChips() { + return isFlagSet(HAS_CODE_PHASE); + } + + /** + * Gets the fraction of the current C/A code cycle. + * Range: [0, 1023] + * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}. + * The reported code-phase includes {@link #getCodePhaseUncertaintyInChips()}. + * + * The value is only available if {@link #hasCodePhaseInChips()} is true. + */ + public double getCodePhaseInChips() { + return mCodePhaseInChips; + } + + /** + * Sets the Code-phase in chips. + */ + public void setCodePhaseInChips(double value) { + setFlag(HAS_CODE_PHASE); + mCodePhaseInChips = value; + } + + /** + * Resets the Code-phase in chips. + */ + public void resetCodePhaseInChips() { + resetFlag(HAS_CODE_PHASE); + mCodePhaseInChips = Double.NaN; + } + + /** + * Returns true if {@link #getCodePhaseUncertaintyInChips()} is available, false otherwise. + */ + public boolean hasCodePhaseUncertaintyInChips() { + return isFlagSet(HAS_CODE_PHASE_UNCERTAINTY); + } + + /** + * Gets the code-phase's uncertainty (1-Sigma) as a fraction of chips. + * The uncertainty is represented as an absolute (single sided) value. + * + * The value is only available if {@link #hasCodePhaseUncertaintyInChips()} is true. + */ + public double getCodePhaseUncertaintyInChips() { + return mCodePhaseUncertaintyInChips; + } + + /** + * Sets the Code-phase's uncertainty (1-Sigma) in fractions of chips. + */ + public void setCodePhaseUncertaintyInChips(double value) { + setFlag(HAS_CODE_PHASE_UNCERTAINTY); + mCodePhaseUncertaintyInChips = value; + } + + /** + * Resets the Code-phase's uncertainty (1-Sigma) in fractions of chips. + */ + public void resetCodePhaseUncertaintyInChips() { + resetFlag(HAS_CODE_PHASE_UNCERTAINTY); + mCodePhaseUncertaintyInChips = Double.NaN; + } + + /** + * Returns true if {@link #getCarrierFrequencyInHz()} is available, false otherwise. + */ + public boolean hasCarrierFrequencyInHz() { + return isFlagSet(HAS_CARRIER_FREQUENCY); + } + + /** + * Gets the carrier frequency at which codes and messages are modulated, it can be L1 or L2. + * If the field is not set, the carrier frequency corresponds to L1. + * + * The value is only available if {@link #hasCarrierFrequencyInHz()} is true. + */ + public float getCarrierFrequencyInHz() { + return mCarrierFrequencyInHz; + } + + /** + * Sets the Carrier frequency (L1 or L2) in Hz. + */ + public void setCarrierFrequencyInHz(float carrierFrequencyInHz) { + setFlag(HAS_CARRIER_FREQUENCY); + mCarrierFrequencyInHz = carrierFrequencyInHz; + } + + /** + * Resets the Carrier frequency (L1 or L2) in Hz. + */ + public void resetCarrierFrequencyInHz() { + resetFlag(HAS_CARRIER_FREQUENCY); + mCarrierFrequencyInHz = Float.NaN; + } + + /** + * Returns true if {@link #getCarrierCycles()} is available, false otherwise. + */ + public boolean hasCarrierCycles() { + return isFlagSet(HAS_CARRIER_CYCLES); + } + + /** + * The number of full carrier cycles between the satellite and the receiver. + * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}. + * + * The value is only available if {@link #hasCarrierCycles()} is true. + */ + public long getCarrierCycles() { + return mCarrierCycles; + } + + /** + * Sets the number of full carrier cycles between the satellite and the receiver. + */ + public void setCarrierCycles(long value) { + setFlag(HAS_CARRIER_CYCLES); + mCarrierCycles = value; + } + + /** + * Resets the number of full carrier cycles between the satellite and the receiver. + */ + public void resetCarrierCycles() { + resetFlag(HAS_CARRIER_CYCLES); + mCarrierCycles = Long.MIN_VALUE; + } + + /** + * Returns true if {@link #getCarrierPhase()} is available, false otherwise. + */ + public boolean hasCarrierPhase() { + return isFlagSet(HAS_CARRIER_PHASE); + } + + /** + * Gets the RF phase detected by the receiver. + * Range: [0.0, 1.0]. + * This is usually the fractional part of the complete carrier phase measurement. + * + * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}. + * The reported carrier-phase includes {@link #getCarrierPhaseUncertainty()}. + * + * The value is only available if {@link #hasCarrierPhase()} is true. + */ + public double getCarrierPhase() { + return mCarrierPhase; + } + + /** + * Sets the RF phase detected by the receiver. + */ + public void setCarrierPhase(double value) { + setFlag(HAS_CARRIER_PHASE); + mCarrierPhase = value; + } + + /** + * Resets the RF phase detected by the receiver. + */ + public void resetCarrierPhase() { + resetFlag(HAS_CARRIER_PHASE); + mCarrierPhase = Double.NaN; + } + + /** + * Returns true if {@link #getCarrierPhaseUncertainty()} is available, false otherwise. + */ + public boolean hasCarrierPhaseUncertainty() { + return isFlagSet(HAS_CARRIER_PHASE_UNCERTAINTY); + } + + /** + * Gets the carrier-phase's uncertainty (1-Sigma). + * The uncertainty is represented as an absolute (single sided) value. + * + * The value is only available if {@link #hasCarrierPhaseUncertainty()} is true. + */ + public double getCarrierPhaseUncertainty() { + return mCarrierPhaseUncertainty; + } + + /** + * Sets the Carrier-phase's uncertainty (1-Sigma) in cycles. + */ + public void setCarrierPhaseUncertainty(double value) { + setFlag(HAS_CARRIER_PHASE_UNCERTAINTY); + mCarrierPhaseUncertainty = value; + } + + /** + * Resets the Carrier-phase's uncertainty (1-Sigma) in cycles. + */ + public void resetCarrierPhaseUncertainty() { + resetFlag(HAS_CARRIER_PHASE_UNCERTAINTY); + mCarrierPhaseUncertainty = Double.NaN; + } + + /** + * Gets a value indicating the 'loss of lock' state of the event. + */ + public byte getLossOfLock() { + return mLossOfLock; + } + + /** + * Sets the 'loss of lock' status. + */ + public void setLossOfLock(byte value) { + mLossOfLock = value; + } + + /** + * Gets a string representation of the 'loss of lock'. + * For internal and logging use only. + */ + private String getLossOfLockString() { + switch (mLossOfLock) { + case LOSS_OF_LOCK_UNKNOWN: + return "Unknown"; + case LOSS_OF_LOCK_OK: + return "Ok"; + case LOSS_OF_LOCK_CYCLE_SLIP: + return "CycleSlip"; + default: + return "<Invalid:" + mLossOfLock + ">"; + } + } + + /** + * Returns true if {@link #getBitNumber()} is available, false otherwise. + */ + public boolean hasBitNumber() { + return isFlagSet(HAS_BIT_NUMBER); + } + + /** + * Gets the number of GPS bits transmitted since Sat-Sun midnight (GPS week). + * + * The value is only available if {@link #hasBitNumber()} is true. + */ + public int getBitNumber() { + return mBitNumber; + } + + /** + * Sets the bit number within the broadcast frame. + */ + public void setBitNumber(int bitNumber) { + setFlag(HAS_BIT_NUMBER); + mBitNumber = bitNumber; + } + + /** + * Resets the bit number within the broadcast frame. + */ + public void resetBitNumber() { + resetFlag(HAS_BIT_NUMBER); + mBitNumber = Integer.MIN_VALUE; + } + + /** + * Returns true if {@link #getTimeFromLastBitInMs()} is available, false otherwise. + */ + public boolean hasTimeFromLastBitInMs() { + return isFlagSet(HAS_TIME_FROM_LAST_BIT); + } + + /** + * Gets the elapsed time since the last received bit in milliseconds. + * Range: [0, 20]. + * + * The value is only available if {@link #hasTimeFromLastBitInMs()} is true. + */ + public short getTimeFromLastBitInMs() { + return mTimeFromLastBitInMs; + } + + /** + * Sets the elapsed time since the last received bit in milliseconds. + */ + public void setTimeFromLastBitInMs(short value) { + setFlag(HAS_TIME_FROM_LAST_BIT); + mTimeFromLastBitInMs = value; + } + + /** + * Resets the elapsed time since the last received bit in milliseconds. + */ + public void resetTimeFromLastBitInMs() { + resetFlag(HAS_TIME_FROM_LAST_BIT); + mTimeFromLastBitInMs = Short.MIN_VALUE; + } + + /** + * Returns true if {@link #getDopplerShiftInHz()} is available, false otherwise. + */ + public boolean hasDopplerShiftInHz() { + return isFlagSet(HAS_DOPPLER_SHIFT); + } + + /** + * Gets the Doppler Shift in Hz. + * A positive value indicates that the SV is moving toward the receiver. + * + * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}. + * The reported doppler shift includes {@link #getDopplerShiftUncertaintyInHz()}. + * + * The value is only available if {@link #hasDopplerShiftInHz()} is true. + */ + public double getDopplerShiftInHz() { + return mDopplerShiftInHz; + } + + /** + * Sets the Doppler shift in Hz. + */ + public void setDopplerShiftInHz(double value) { + setFlag(HAS_DOPPLER_SHIFT); + mDopplerShiftInHz = value; + } + + /** + * Resets the Doppler shift in Hz. + */ + public void resetDopplerShiftInHz() { + resetFlag(HAS_DOPPLER_SHIFT); + mDopplerShiftInHz = Double.NaN; + } + + /** + * Returns true if {@link #getDopplerShiftUncertaintyInHz()} is available, false otherwise. + */ + public boolean hasDopplerShiftUncertaintyInHz() { + return isFlagSet(HAS_DOPPLER_SHIFT_UNCERTAINTY); + } + + /** + * Gets the Doppler's Shift uncertainty (1-Sigma) in Hz. + * The uncertainty is represented as an absolute (single sided) value. + * + * The value is only available if {@link #hasDopplerShiftUncertaintyInHz()} is true. + */ + public double getDopplerShiftUncertaintyInHz() { + return mDopplerShiftUncertaintyInHz; + } + + /** + * Sets the Doppler's shift uncertainty (1-Sigma) in Hz. + */ + public void setDopplerShiftUncertaintyInHz(double value) { + setFlag(HAS_DOPPLER_SHIFT_UNCERTAINTY); + mDopplerShiftUncertaintyInHz = value; + } + + /** + * Resets the Doppler's shift uncertainty (1-Sigma) in Hz. + */ + public void resetDopplerShiftUncertaintyInHz() { + resetFlag(HAS_DOPPLER_SHIFT_UNCERTAINTY); + mDopplerShiftUncertaintyInHz = Double.NaN; + } + + /** + * Gets a value indicating the 'multipath' state of the event. + */ + public byte getMultipathIndicator() { + return mMultipathIndicator; + } + + /** + * Sets the 'multi-path' indicator. + */ + public void setMultipathIndicator(byte value) { + mMultipathIndicator = value; + } + + /** + * Gets a string representation of the 'multi-path indicator'. + * For internal and logging use only. + */ + private String getMultipathIndicatorString() { + switch(mMultipathIndicator) { + case MULTIPATH_INDICATOR_UNKNOWN: + return "Unknown"; + case MULTIPATH_INDICATOR_DETECTED: + return "Detected"; + case MULTIPATH_INDICATOR_NOT_USED: + return "NotUsed"; + default: + return "<Invalid:" + mMultipathIndicator + ">"; + } + } + + /** + * Returns true if {@link #getSnrInDb()} is available, false otherwise. + */ + public boolean hasSnrInDb() { + return isFlagSet(HAS_SNR); + } + + /** + * Gets the Signal-to-Noise ratio (SNR) in dB. + * + * The value is only available if {@link #hasSnrInDb()} is true. + */ + public double getSnrInDb() { + return mSnrInDb; + } + + /** + * Sets the Signal-to-noise ratio (SNR) in dB. + */ + public void setSnrInDb(double snrInDb) { + setFlag(HAS_SNR); + mSnrInDb = snrInDb; + } + + /** + * Resets the Signal-to-noise ratio (SNR) in dB. + */ + public void resetSnrInDb() { + resetFlag(HAS_SNR); + mSnrInDb = Double.NaN; + } + + /** + * Returns true if {@link #getElevationInDeg()} is available, false otherwise. + */ + public boolean hasElevationInDeg() { + return isFlagSet(HAS_ELEVATION); + } + + /** + * Gets the Elevation in degrees. + * Range: [-90, 90] + * The reported elevation includes {@link #getElevationUncertaintyInDeg()}. + * + * The value is only available if {@link #hasElevationInDeg()} is true. + */ + public double getElevationInDeg() { + return mElevationInDeg; + } + + /** + * Sets the Elevation in degrees. + */ + public void setElevationInDeg(double elevationInDeg) { + setFlag(HAS_ELEVATION); + mElevationInDeg = elevationInDeg; + } + + /** + * Resets the Elevation in degrees. + */ + public void resetElevationInDeg() { + resetFlag(HAS_ELEVATION); + mElevationInDeg = Double.NaN; + } + + /** + * Returns true if {@link #getElevationUncertaintyInDeg()} is available, false otherwise. + */ + public boolean hasElevationUncertaintyInDeg() { + return isFlagSet(HAS_ELEVATION_UNCERTAINTY); + } + + /** + * Gets the elevation's uncertainty (1-Sigma) in degrees. + * Range: [0, 90] + * + * The uncertainty is represented as an absolute (single sided) value. + * + * The value is only available if {@link #hasElevationUncertaintyInDeg()} is true. + */ + public double getElevationUncertaintyInDeg() { + return mElevationUncertaintyInDeg; + } + + /** + * Sets the elevation's uncertainty (1-Sigma) in degrees. + */ + public void setElevationUncertaintyInDeg(double value) { + setFlag(HAS_ELEVATION_UNCERTAINTY); + mElevationUncertaintyInDeg = value; + } + + /** + * Resets the elevation's uncertainty (1-Sigma) in degrees. + */ + public void resetElevationUncertaintyInDeg() { + resetFlag(HAS_ELEVATION_UNCERTAINTY); + mElevationUncertaintyInDeg = Double.NaN; + } + + /** + * Returns true if {@link #getAzimuthInDeg()} is available, false otherwise. + */ + public boolean hasAzimuthInDeg() { + return isFlagSet(HAS_AZIMUTH); + } + + /** + * Gets the azimuth in degrees. + * Range: [0, 360). + * + * The reported azimuth includes {@link #getAzimuthUncertaintyInDeg()}. + * + * The value is only available if {@link #hasAzimuthInDeg()} is true. + */ + public double getAzimuthInDeg() { + return mAzimuthInDeg; + } + + /** + * Sets the Azimuth in degrees. + */ + public void setAzimuthInDeg(double value) { + setFlag(HAS_AZIMUTH); + mAzimuthInDeg = value; + } + + /** + * Resets the Azimuth in degrees. + */ + public void resetAzimuthInDeg() { + resetFlag(HAS_AZIMUTH); + mAzimuthInDeg = Double.NaN; + } + + /** + * Returns true if {@link #getAzimuthUncertaintyInDeg()} is available, false otherwise. + */ + public boolean hasAzimuthUncertaintyInDeg() { + return isFlagSet(HAS_AZIMUTH_UNCERTAINTY); + } + + /** + * Gets the azimuth's uncertainty (1-Sigma) in degrees. + * Range: [0, 180]. + * + * The uncertainty is represented as an absolute (single sided) value. + * + * The value is only available if {@link #hasAzimuthUncertaintyInDeg()} is true. + */ + public double getAzimuthUncertaintyInDeg() { + return mAzimuthUncertaintyInDeg; + } + + /** + * Sets the Azimuth's uncertainty (1-Sigma) in degrees. + */ + public void setAzimuthUncertaintyInDeg(double value) { + setFlag(HAS_AZIMUTH_UNCERTAINTY); + mAzimuthUncertaintyInDeg = value; + } + + /** + * Resets the Azimuth's uncertainty (1-Sigma) in degrees. + */ + public void resetAzimuthUncertaintyInDeg() { + resetFlag(HAS_AZIMUTH_UNCERTAINTY); + mAzimuthUncertaintyInDeg = Double.NaN; + } + + /** + * Gets a flag indicating whether the GPS represented by the measurement was used for computing + * the most recent fix. + * + * @return A non-null value if the data is available, null otherwise. + */ + public boolean isUsedInFix() { + return mUsedInFix; + } + + /** + * Sets the Used-in-Fix flag. + */ + public void setUsedInFix(boolean value) { + mUsedInFix = value; + } + + public static final Creator<GpsMeasurement> CREATOR = new Creator<GpsMeasurement>() { + @Override + public GpsMeasurement createFromParcel(Parcel parcel) { + GpsMeasurement gpsMeasurement = new GpsMeasurement(); + + gpsMeasurement.mFlags = parcel.readInt(); + gpsMeasurement.mPrn = parcel.readByte(); + gpsMeasurement.mTimeOffsetInNs = parcel.readDouble(); + gpsMeasurement.mState = (short) parcel.readInt(); + gpsMeasurement.mReceivedGpsTowInNs = parcel.readLong(); + gpsMeasurement.mReceivedGpsTowUncertaintyInNs = parcel.readLong(); + gpsMeasurement.mCn0InDbHz = parcel.readDouble(); + gpsMeasurement.mPseudorangeRateInMetersPerSec = parcel.readDouble(); + gpsMeasurement.mPseudorangeRateUncertaintyInMetersPerSec = parcel.readDouble(); + gpsMeasurement.mAccumulatedDeltaRangeState = (short) parcel.readInt(); + gpsMeasurement.mAccumulatedDeltaRangeInMeters = parcel.readDouble(); + gpsMeasurement.mAccumulatedDeltaRangeUncertaintyInMeters = parcel.readDouble(); + gpsMeasurement.mPseudorangeInMeters = parcel.readDouble(); + gpsMeasurement.mPseudorangeUncertaintyInMeters = parcel.readDouble(); + gpsMeasurement.mCodePhaseInChips = parcel.readDouble(); + gpsMeasurement.mCodePhaseUncertaintyInChips = parcel.readDouble(); + gpsMeasurement.mCarrierFrequencyInHz = parcel.readFloat(); + gpsMeasurement.mCarrierCycles = parcel.readLong(); + gpsMeasurement.mCarrierPhase = parcel.readDouble(); + gpsMeasurement.mCarrierPhaseUncertainty = parcel.readDouble(); + gpsMeasurement.mLossOfLock = parcel.readByte(); + gpsMeasurement.mBitNumber = parcel.readInt(); + gpsMeasurement.mTimeFromLastBitInMs = (short) parcel.readInt(); + gpsMeasurement.mDopplerShiftInHz = parcel.readDouble(); + gpsMeasurement.mDopplerShiftUncertaintyInHz = parcel.readDouble(); + gpsMeasurement.mMultipathIndicator = parcel.readByte(); + gpsMeasurement.mSnrInDb = parcel.readDouble(); + gpsMeasurement.mElevationInDeg = parcel.readDouble(); + gpsMeasurement.mElevationUncertaintyInDeg = parcel.readDouble(); + gpsMeasurement.mAzimuthInDeg = parcel.readDouble(); + gpsMeasurement.mAzimuthUncertaintyInDeg = parcel.readDouble(); + gpsMeasurement.mUsedInFix = parcel.readInt() != 0; + + return gpsMeasurement; + } + + @Override + public GpsMeasurement[] newArray(int i) { + return new GpsMeasurement[i]; + } + }; + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mFlags); + parcel.writeByte(mPrn); + parcel.writeDouble(mTimeOffsetInNs); + parcel.writeInt(mState); + parcel.writeLong(mReceivedGpsTowInNs); + parcel.writeLong(mReceivedGpsTowUncertaintyInNs); + parcel.writeDouble(mCn0InDbHz); + parcel.writeDouble(mPseudorangeRateInMetersPerSec); + parcel.writeDouble(mPseudorangeRateUncertaintyInMetersPerSec); + parcel.writeInt(mAccumulatedDeltaRangeState); + parcel.writeDouble(mAccumulatedDeltaRangeInMeters); + parcel.writeDouble(mAccumulatedDeltaRangeUncertaintyInMeters); + parcel.writeDouble(mPseudorangeInMeters); + parcel.writeDouble(mPseudorangeUncertaintyInMeters); + parcel.writeDouble(mCodePhaseInChips); + parcel.writeDouble(mCodePhaseUncertaintyInChips); + parcel.writeFloat(mCarrierFrequencyInHz); + parcel.writeLong(mCarrierCycles); + parcel.writeDouble(mCarrierPhase); + parcel.writeDouble(mCarrierPhaseUncertainty); + parcel.writeByte(mLossOfLock); + parcel.writeInt(mBitNumber); + parcel.writeInt(mTimeFromLastBitInMs); + parcel.writeDouble(mDopplerShiftInHz); + parcel.writeDouble(mDopplerShiftUncertaintyInHz); + parcel.writeByte(mMultipathIndicator); + parcel.writeDouble(mSnrInDb); + parcel.writeDouble(mElevationInDeg); + parcel.writeDouble(mElevationUncertaintyInDeg); + parcel.writeDouble(mAzimuthInDeg); + parcel.writeDouble(mAzimuthUncertaintyInDeg); + parcel.writeInt(mUsedInFix ? 1 : 0); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + final String format = " %-29s = %s\n"; + final String formatWithUncertainty = " %-29s = %-25s %-40s = %s\n"; + StringBuilder builder = new StringBuilder("GpsMeasurement:\n"); + + builder.append(String.format(format, "Prn", mPrn)); + + builder.append(String.format(format, "TimeOffsetInNs", mTimeOffsetInNs)); + + builder.append(String.format(format, "State", getStateString())); + + builder.append(String.format( + formatWithUncertainty, + "ReceivedGpsTowInNs", + mReceivedGpsTowInNs, + "ReceivedGpsTowUncertaintyInNs", + mReceivedGpsTowUncertaintyInNs)); + + builder.append(String.format(format, "Cn0InDbHz", mCn0InDbHz)); + + builder.append(String.format( + formatWithUncertainty, + "PseudorangeRateInMetersPerSec", + mPseudorangeRateInMetersPerSec, + "PseudorangeRateUncertaintyInMetersPerSec", + mPseudorangeRateUncertaintyInMetersPerSec)); + builder.append(String.format( + format, + "PseudorangeRateIsCorrected", + isPseudorangeRateCorrected())); + + builder.append(String.format( + format, + "AccumulatedDeltaRangeState", + getAccumulatedDeltaRangeStateString())); + + builder.append(String.format( + formatWithUncertainty, + "AccumulatedDeltaRangeInMeters", + mAccumulatedDeltaRangeInMeters, + "AccumulatedDeltaRangeUncertaintyInMeters", + mAccumulatedDeltaRangeUncertaintyInMeters)); + + builder.append(String.format( + formatWithUncertainty, + "PseudorangeInMeters", + hasPseudorangeInMeters() ? mPseudorangeInMeters : null, + "PseudorangeUncertaintyInMeters", + hasPseudorangeUncertaintyInMeters() ? mPseudorangeUncertaintyInMeters : null)); + + builder.append(String.format( + formatWithUncertainty, + "CodePhaseInChips", + hasCodePhaseInChips() ? mCodePhaseInChips : null, + "CodePhaseUncertaintyInChips", + hasCodePhaseUncertaintyInChips() ? mCodePhaseUncertaintyInChips : null)); + + builder.append(String.format( + format, + "CarrierFrequencyInHz", + hasCarrierFrequencyInHz() ? mCarrierFrequencyInHz : null)); + + builder.append(String.format( + format, + "CarrierCycles", + hasCarrierCycles() ? mCarrierCycles : null)); + + builder.append(String.format( + formatWithUncertainty, + "CarrierPhase", + hasCarrierPhase() ? mCarrierPhase : null, + "CarrierPhaseUncertainty", + hasCarrierPhaseUncertainty() ? mCarrierPhaseUncertainty : null)); + + builder.append(String.format(format, "LossOfLock", getLossOfLockString())); + + builder.append(String.format( + format, + "BitNumber", + hasBitNumber() ? mBitNumber : null)); + + builder.append(String.format( + format, + "TimeFromLastBitInMs", + hasTimeFromLastBitInMs() ? mTimeFromLastBitInMs : null)); + + builder.append(String.format( + formatWithUncertainty, + "DopplerShiftInHz", + hasDopplerShiftInHz() ? mDopplerShiftInHz : null, + "DopplerShiftUncertaintyInHz", + hasDopplerShiftUncertaintyInHz() ? mDopplerShiftUncertaintyInHz : null)); + + builder.append(String.format(format, "MultipathIndicator", getMultipathIndicatorString())); + + builder.append(String.format( + format, + "SnrInDb", + hasSnrInDb() ? mSnrInDb : null)); + + builder.append(String.format( + formatWithUncertainty, + "ElevationInDeg", + hasElevationInDeg() ? mElevationInDeg : null, + "ElevationUncertaintyInDeg", + hasElevationUncertaintyInDeg() ? mElevationUncertaintyInDeg : null)); + + builder.append(String.format( + formatWithUncertainty, + "AzimuthInDeg", + hasAzimuthInDeg() ? mAzimuthInDeg : null, + "AzimuthUncertaintyInDeg", + hasAzimuthUncertaintyInDeg() ? mAzimuthUncertaintyInDeg : null)); + + builder.append(String.format(format, "UsedInFix", mUsedInFix)); + + return builder.toString(); + } + + private void initialize() { + mFlags = HAS_NO_FLAGS; + setPrn(Byte.MIN_VALUE); + setTimeOffsetInNs(Long.MIN_VALUE); + setState(STATE_UNKNOWN); + setReceivedGpsTowInNs(Long.MIN_VALUE); + setReceivedGpsTowUncertaintyInNs(Long.MAX_VALUE); + setCn0InDbHz(Double.MIN_VALUE); + setPseudorangeRateInMetersPerSec(Double.MIN_VALUE); + setPseudorangeRateUncertaintyInMetersPerSec(Double.MIN_VALUE); + setAccumulatedDeltaRangeState(ADR_STATE_UNKNOWN); + setAccumulatedDeltaRangeInMeters(Double.MIN_VALUE); + setAccumulatedDeltaRangeUncertaintyInMeters(Double.MIN_VALUE); + resetPseudorangeInMeters(); + resetPseudorangeUncertaintyInMeters(); + resetCodePhaseInChips(); + resetCodePhaseUncertaintyInChips(); + resetCarrierFrequencyInHz(); + resetCarrierCycles(); + resetCarrierPhase(); + resetCarrierPhaseUncertainty(); + setLossOfLock(LOSS_OF_LOCK_UNKNOWN); + resetBitNumber(); + resetTimeFromLastBitInMs(); + resetDopplerShiftInHz(); + resetDopplerShiftUncertaintyInHz(); + setMultipathIndicator(MULTIPATH_INDICATOR_UNKNOWN); + resetSnrInDb(); + resetElevationInDeg(); + resetElevationUncertaintyInDeg(); + resetAzimuthInDeg(); + resetAzimuthUncertaintyInDeg(); + setUsedInFix(false); + } + + private void setFlag(int flag) { + mFlags |= flag; + } + + private void resetFlag(int flag) { + mFlags &= ~flag; + } + + private boolean isFlagSet(int flag) { + return (mFlags & flag) == flag; + } +} diff --git a/android/location/GpsMeasurementsEvent.java b/android/location/GpsMeasurementsEvent.java new file mode 100644 index 00000000..13668733 --- /dev/null +++ b/android/location/GpsMeasurementsEvent.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2014 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.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.security.InvalidParameterException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * A class implementing a container for data associated with a measurement event. + * Events are delivered to registered instances of {@link Listener}. + * + * @hide + */ +@SystemApi +public class GpsMeasurementsEvent implements Parcelable { + + /** + * The system does not support tracking of GPS Measurements. This status will not change in the + * future. + */ + public static final int STATUS_NOT_SUPPORTED = 0; + + /** + * GPS Measurements are successfully being tracked, it will receive updates once they are + * available. + */ + public static final int STATUS_READY = 1; + + /** + * GPS provider or Location is disabled, updates will not be received until they are enabled. + */ + public static final int STATUS_GPS_LOCATION_DISABLED = 2; + + private final GpsClock mClock; + private final Collection<GpsMeasurement> mReadOnlyMeasurements; + + /** + * Used for receiving GPS satellite measurements from the GPS engine. + * Each measurement contains raw and computed data identifying a satellite. + * You can implement this interface and call {@link LocationManager#addGpsMeasurementListener}. + * + * @hide + */ + @SystemApi + public interface Listener { + + /** + * Returns the latest collected GPS Measurements. + */ + void onGpsMeasurementsReceived(GpsMeasurementsEvent eventArgs); + + /** + * Returns the latest status of the GPS Measurements sub-system. + */ + void onStatusChanged(int status); + } + + public GpsMeasurementsEvent(GpsClock clock, GpsMeasurement[] measurements) { + if (clock == null) { + throw new InvalidParameterException("Parameter 'clock' must not be null."); + } + if (measurements == null || measurements.length == 0) { + throw new InvalidParameterException( + "Parameter 'measurements' must not be null or empty."); + } + + mClock = clock; + Collection<GpsMeasurement> measurementCollection = Arrays.asList(measurements); + mReadOnlyMeasurements = Collections.unmodifiableCollection(measurementCollection); + } + + @NonNull + public GpsClock getClock() { + return mClock; + } + + /** + * Gets a read-only collection of measurements associated with the current event. + */ + @NonNull + public Collection<GpsMeasurement> getMeasurements() { + return mReadOnlyMeasurements; + } + + public static final Creator<GpsMeasurementsEvent> CREATOR = + new Creator<GpsMeasurementsEvent>() { + @Override + public GpsMeasurementsEvent createFromParcel(Parcel in) { + ClassLoader classLoader = getClass().getClassLoader(); + + GpsClock clock = in.readParcelable(classLoader); + + int measurementsLength = in.readInt(); + GpsMeasurement[] measurementsArray = new GpsMeasurement[measurementsLength]; + in.readTypedArray(measurementsArray, GpsMeasurement.CREATOR); + + return new GpsMeasurementsEvent(clock, measurementsArray); + } + + @Override + public GpsMeasurementsEvent[] newArray(int size) { + return new GpsMeasurementsEvent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mClock, flags); + + int measurementsCount = mReadOnlyMeasurements.size(); + GpsMeasurement[] measurementsArray = + mReadOnlyMeasurements.toArray(new GpsMeasurement[measurementsCount]); + parcel.writeInt(measurementsArray.length); + parcel.writeTypedArray(measurementsArray, flags); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("[ GpsMeasurementsEvent:\n\n"); + + builder.append(mClock.toString()); + builder.append("\n"); + + for (GpsMeasurement measurement : mReadOnlyMeasurements) { + builder.append(measurement.toString()); + builder.append("\n"); + } + + builder.append("]"); + + return builder.toString(); + } +} diff --git a/android/location/GpsNavigationMessage.java b/android/location/GpsNavigationMessage.java new file mode 100644 index 00000000..5c3c7101 --- /dev/null +++ b/android/location/GpsNavigationMessage.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2014 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.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.security.InvalidParameterException; + +/** + * A class containing a GPS satellite Navigation Message. + * + * @hide + */ +@SystemApi +public class GpsNavigationMessage implements Parcelable { + + private static final byte[] EMPTY_ARRAY = new byte[0]; + + // The following enumerations must be in sync with the values declared in gps.h + + /** + * The type of the navigation message is not available or unknown. + */ + public static final byte TYPE_UNKNOWN = 0; + + /** + * The Navigation Message is of type L1 C/A. + */ + public static final byte TYPE_L1CA = 1; + + /** + * The Navigation Message is of type L1-CNAV. + */ + public static final byte TYPE_L2CNAV = 2; + + /** + * The Navigation Message is of type L5-CNAV. + */ + public static final byte TYPE_L5CNAV = 3; + + /** + * The Navigation Message is of type CNAV-2. + */ + public static final byte TYPE_CNAV2 = 4; + + /** + * The Navigation Message Status is 'unknown'. + */ + public static final short STATUS_UNKNOWN = 0; + + /** + * The Navigation Message was received without any parity error in its navigation words. + */ + public static final short STATUS_PARITY_PASSED = (1<<0); + + /** + * The Navigation Message was received with words that failed parity check, but the receiver was + * able to correct those words. + */ + public static final short STATUS_PARITY_REBUILT = (1<<1); + + // End enumerations in sync with gps.h + + private byte mType; + private byte mPrn; + private short mMessageId; + private short mSubmessageId; + private byte[] mData; + private short mStatus; + + GpsNavigationMessage() { + initialize(); + } + + /** + * Sets all contents to the values stored in the provided object. + */ + public void set(GpsNavigationMessage navigationMessage) { + mType = navigationMessage.mType; + mPrn = navigationMessage.mPrn; + mMessageId = navigationMessage.mMessageId; + mSubmessageId = navigationMessage.mSubmessageId; + mData = navigationMessage.mData; + mStatus = navigationMessage.mStatus; + } + + /** + * Resets all the contents to its original state. + */ + public void reset() { + initialize(); + } + + /** + * Gets the type of the navigation message contained in the object. + */ + public byte getType() { + return mType; + } + + /** + * Sets the type of the navigation message. + */ + public void setType(byte value) { + mType = value; + } + + /** + * Gets a string representation of the 'type'. + * For internal and logging use only. + */ + private String getTypeString() { + switch (mType) { + case TYPE_UNKNOWN: + return "Unknown"; + case TYPE_L1CA: + return "L1 C/A"; + case TYPE_L2CNAV: + return "L2-CNAV"; + case TYPE_L5CNAV: + return "L5-CNAV"; + case TYPE_CNAV2: + return "CNAV-2"; + default: + return "<Invalid:" + mType + ">"; + } + } + + /** + * Gets the Pseudo-random number. + * Range: [1, 32]. + */ + public byte getPrn() { + return mPrn; + } + + /** + * Sets the Pseud-random number. + */ + public void setPrn(byte value) { + mPrn = value; + } + + /** + * Gets the Message Identifier. + * It provides an index so the complete Navigation Message can be assembled. i.e. for L1 C/A + * subframe 4 and 5, this value corresponds to the 'frame id' of the navigation message. + * Subframe 1, 2, 3 does not contain a 'frame id' and this might be reported as -1. + */ + public short getMessageId() { + return mMessageId; + } + + /** + * Sets the Message Identifier. + */ + public void setMessageId(short value) { + mMessageId = value; + } + + /** + * Gets the Sub-message Identifier. + * If required by {@link #getType()}, this value contains a sub-index within the current message + * (or frame) that is being transmitted. i.e. for L1 C/A the sub-message identifier corresponds + * to the sub-frame Id of the navigation message. + */ + public short getSubmessageId() { + return mSubmessageId; + } + + /** + * Sets the Sub-message identifier. + */ + public void setSubmessageId(short value) { + mSubmessageId = value; + } + + /** + * Gets the data associated with the Navigation Message. + * The bytes (or words) specified using big endian format (MSB first). + */ + @NonNull + public byte[] getData() { + return mData; + } + + /** + * Sets the data associated with the Navigation Message. + */ + public void setData(byte[] value) { + if (value == null) { + throw new InvalidParameterException("Data must be a non-null array"); + } + + mData = value; + } + + /** + * Gets the Status of the navigation message contained in the object. + */ + public short getStatus() { + return mStatus; + } + + /** + * Sets the status of the navigation message. + */ + public void setStatus(short value) { + mStatus = value; + } + + /** + * Gets a string representation of the 'status'. + * For internal and logging use only. + */ + private String getStatusString() { + switch (mStatus) { + case STATUS_UNKNOWN: + return "Unknown"; + case STATUS_PARITY_PASSED: + return "ParityPassed"; + case STATUS_PARITY_REBUILT: + return "ParityRebuilt"; + default: + return "<Invalid:" + mStatus + ">"; + } + } + + public static final Creator<GpsNavigationMessage> CREATOR = + new Creator<GpsNavigationMessage>() { + @Override + public GpsNavigationMessage createFromParcel(Parcel parcel) { + GpsNavigationMessage navigationMessage = new GpsNavigationMessage(); + + navigationMessage.setType(parcel.readByte()); + navigationMessage.setPrn(parcel.readByte()); + navigationMessage.setMessageId((short) parcel.readInt()); + navigationMessage.setSubmessageId((short) parcel.readInt()); + + int dataLength = parcel.readInt(); + byte[] data = new byte[dataLength]; + parcel.readByteArray(data); + navigationMessage.setData(data); + + if (parcel.dataAvail() >= Integer.SIZE) { + int status = parcel.readInt(); + navigationMessage.setStatus((short) status); + } else { + navigationMessage.setStatus(STATUS_UNKNOWN); + } + + return navigationMessage; + } + + @Override + public GpsNavigationMessage[] newArray(int size) { + return new GpsNavigationMessage[size]; + } + }; + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeByte(mType); + parcel.writeByte(mPrn); + parcel.writeInt(mMessageId); + parcel.writeInt(mSubmessageId); + parcel.writeInt(mData.length); + parcel.writeByteArray(mData); + parcel.writeInt(mStatus); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + final String format = " %-15s = %s\n"; + StringBuilder builder = new StringBuilder("GpsNavigationMessage:\n"); + + builder.append(String.format(format, "Type", getTypeString())); + builder.append(String.format(format, "Prn", mPrn)); + builder.append(String.format(format, "Status", getStatusString())); + builder.append(String.format(format, "MessageId", mMessageId)); + builder.append(String.format(format, "SubmessageId", mSubmessageId)); + + builder.append(String.format(format, "Data", "{")); + String prefix = " "; + for(byte value : mData) { + builder.append(prefix); + builder.append(value); + prefix = ", "; + } + builder.append(" }"); + + return builder.toString(); + } + + private void initialize() { + mType = TYPE_UNKNOWN; + mPrn = 0; + mMessageId = -1; + mSubmessageId = -1; + mData = EMPTY_ARRAY; + mStatus = STATUS_UNKNOWN; + } +} diff --git a/android/location/GpsNavigationMessageEvent.java b/android/location/GpsNavigationMessageEvent.java new file mode 100644 index 00000000..bd6921c7 --- /dev/null +++ b/android/location/GpsNavigationMessageEvent.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 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.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.security.InvalidParameterException; + +/** + * A class implementing a container for data associated with a navigation message event. + * Events are delivered to registered instances of {@link Listener}. + * + * @hide + */ +@SystemApi +public class GpsNavigationMessageEvent implements Parcelable { + + /** + * The system does not support tracking of GPS Navigation Messages. This status will not change + * in the future. + */ + public static int STATUS_NOT_SUPPORTED = 0; + + /** + * GPS Navigation Messages are successfully being tracked, it will receive updates once they are + * available. + */ + public static int STATUS_READY = 1; + + /** + * GPS provider or Location is disabled, updated will not be received until they are enabled. + */ + public static int STATUS_GPS_LOCATION_DISABLED = 2; + + private final GpsNavigationMessage mNavigationMessage; + + /** + * Used for receiving GPS satellite Navigation Messages from the GPS engine. + * You can implement this interface and call + * {@link LocationManager#addGpsNavigationMessageListener}. + * + * @hide + */ + @SystemApi + public interface Listener { + + /** + * Returns the latest collected GPS Navigation Message. + */ + void onGpsNavigationMessageReceived(GpsNavigationMessageEvent event); + + /** + * Returns the latest status of the GPS Navigation Messages sub-system. + */ + void onStatusChanged(int status); + } + + public GpsNavigationMessageEvent(GpsNavigationMessage message) { + if (message == null) { + throw new InvalidParameterException("Parameter 'message' must not be null."); + } + mNavigationMessage = message; + } + + @NonNull + public GpsNavigationMessage getNavigationMessage() { + return mNavigationMessage; + } + + public static final Creator<GpsNavigationMessageEvent> CREATOR = + new Creator<GpsNavigationMessageEvent>() { + @Override + public GpsNavigationMessageEvent createFromParcel(Parcel in) { + ClassLoader classLoader = getClass().getClassLoader(); + GpsNavigationMessage navigationMessage = in.readParcelable(classLoader); + return new GpsNavigationMessageEvent(navigationMessage); + } + + @Override + public GpsNavigationMessageEvent[] newArray(int size) { + return new GpsNavigationMessageEvent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mNavigationMessage, flags); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("[ GpsNavigationMessageEvent:\n\n"); + builder.append(mNavigationMessage.toString()); + builder.append("\n]"); + return builder.toString(); + } +} diff --git a/android/location/GpsSatellite.java b/android/location/GpsSatellite.java new file mode 100644 index 00000000..788d01ec --- /dev/null +++ b/android/location/GpsSatellite.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008 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; + +/** + * This class represents the current state of a GPS satellite. + * + * This class is used in conjunction with the {@link GpsStatus} class. + * + * @deprecated use {@link GnssStatus} and {@link GnssStatus.Callback}. + */ +@Deprecated +public final class GpsSatellite { + /* These package private values are modified by the GpsStatus class */ + boolean mValid; + boolean mHasEphemeris; + boolean mHasAlmanac; + boolean mUsedInFix; + int mPrn; + float mSnr; + float mElevation; + float mAzimuth; + + GpsSatellite(int prn) { + mPrn = prn; + } + + /** + * Used by {@link LocationManager#getGpsStatus} to copy LocationManager's + * cached GpsStatus instance to the client's copy. + */ + void setStatus(GpsSatellite satellite) { + if (satellite == null) { + mValid = false; + } else { + mValid = satellite.mValid; + mHasEphemeris = satellite.mHasEphemeris; + mHasAlmanac = satellite.mHasAlmanac; + mUsedInFix = satellite.mUsedInFix; + mSnr = satellite.mSnr; + mElevation = satellite.mElevation; + mAzimuth = satellite.mAzimuth; + } + } + + /** + * Returns the PRN (pseudo-random number) for the satellite. + * + * @return PRN number + */ + public int getPrn() { + return mPrn; + } + + /** + * Returns the signal to noise ratio for the satellite. + * + * @return the signal to noise ratio + */ + public float getSnr() { + return mSnr; + } + + /** + * Returns the elevation of the satellite in degrees. + * The elevation can vary between 0 and 90. + * + * @return the elevation in degrees + */ + public float getElevation() { + return mElevation; + } + + /** + * Returns the azimuth of the satellite in degrees. + * The azimuth can vary between 0 and 360. + * + * @return the azimuth in degrees + */ + public float getAzimuth() { + return mAzimuth; + } + + /** + * Returns true if the GPS engine has ephemeris data for the satellite. + * + * @return true if the satellite has ephemeris data + */ + public boolean hasEphemeris() { + return mHasEphemeris; + } + + /** + * Returns true if the GPS engine has almanac data for the satellite. + * + * @return true if the satellite has almanac data + */ + public boolean hasAlmanac() { + return mHasAlmanac; + } + + /** + * Returns true if the satellite was used by the GPS engine when + * calculating the most recent GPS fix. + * + * @return true if the satellite was used to compute the most recent fix. + */ + public boolean usedInFix() { + return mUsedInFix; + } +} diff --git a/android/location/GpsStatus.java b/android/location/GpsStatus.java new file mode 100644 index 00000000..b601cde3 --- /dev/null +++ b/android/location/GpsStatus.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2008 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.util.SparseArray; + +import java.util.Iterator; +import java.util.NoSuchElementException; + + +/** + * This class represents the current state of the GPS engine. + * + * <p>This class is used in conjunction with the {@link Listener} interface. + * + * @deprecated use {@link GnssStatus} and {@link GnssStatus.Callback}. + */ +@Deprecated +public final class GpsStatus { + private static final int NUM_SATELLITES = 255; + private static final int GLONASS_SVID_OFFSET = 64; + private static final int BEIDOU_SVID_OFFSET = 200; + private static final int SBAS_SVID_OFFSET = -87; + + /* These package private values are modified by the LocationManager class */ + private int mTimeToFirstFix; + private final SparseArray<GpsSatellite> mSatellites = new SparseArray<>(); + + private final class SatelliteIterator implements Iterator<GpsSatellite> { + private final int mSatellitesCount; + + private int mIndex = 0; + + SatelliteIterator() { + mSatellitesCount = mSatellites.size(); + } + + @Override + public boolean hasNext() { + for (; mIndex < mSatellitesCount; ++mIndex) { + GpsSatellite satellite = mSatellites.valueAt(mIndex); + if (satellite.mValid) { + return true; + } + } + return false; + } + + @Override + public GpsSatellite next() { + while (mIndex < mSatellitesCount) { + GpsSatellite satellite = mSatellites.valueAt(mIndex); + ++mIndex; + if (satellite.mValid) { + return satellite; + } + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private Iterable<GpsSatellite> mSatelliteList = new Iterable<GpsSatellite>() { + @Override + public Iterator<GpsSatellite> iterator() { + return new SatelliteIterator(); + } + }; + + /** + * Event sent when the GPS system has started. + */ + public static final int GPS_EVENT_STARTED = 1; + + /** + * Event sent when the GPS system has stopped. + */ + public static final int GPS_EVENT_STOPPED = 2; + + /** + * Event sent when the GPS system has received its first fix since starting. + * Call {@link #getTimeToFirstFix()} to find the time from start to first fix. + */ + public static final int GPS_EVENT_FIRST_FIX = 3; + + /** + * Event sent periodically to report GPS satellite status. + * Call {@link #getSatellites()} to retrieve the status for each satellite. + */ + public static final int GPS_EVENT_SATELLITE_STATUS = 4; + + /** + * Used for receiving notifications when GPS status has changed. + * @deprecated use {@link GnssStatus.Callback} instead. + */ + @Deprecated + public interface Listener { + /** + * Called to report changes in the GPS status. + * The event number is one of: + * <ul> + * <li> {@link GpsStatus#GPS_EVENT_STARTED} + * <li> {@link GpsStatus#GPS_EVENT_STOPPED} + * <li> {@link GpsStatus#GPS_EVENT_FIRST_FIX} + * <li> {@link GpsStatus#GPS_EVENT_SATELLITE_STATUS} + * </ul> + * + * When this method is called, the client should call + * {@link LocationManager#getGpsStatus} to get additional + * status information. + * + * @param event event number for this notification + */ + void onGpsStatusChanged(int event); + } + + /** + * Used for receiving NMEA sentences from the GPS. + * NMEA 0183 is a standard for communicating with marine electronic devices + * and is a common method for receiving data from a GPS, typically over a serial port. + * See <a href="http://en.wikipedia.org/wiki/NMEA_0183">NMEA 0183</a> for more details. + * You can implement this interface and call {@link LocationManager#addNmeaListener} + * to receive NMEA data from the GPS engine. + * @deprecated use {@link OnNmeaMessageListener} instead. + */ + @Deprecated + public interface NmeaListener { + void onNmeaReceived(long timestamp, String nmea); + } + + // For API-compat a public ctor() is not available + GpsStatus() {} + + private void setStatus(int svCount, int[] svidWithFlags, float[] cn0s, float[] elevations, + float[] azimuths) { + clearSatellites(); + for (int i = 0; i < svCount; i++) { + final int constellationType = + (svidWithFlags[i] >> GnssStatus.CONSTELLATION_TYPE_SHIFT_WIDTH) + & GnssStatus.CONSTELLATION_TYPE_MASK; + int prn = svidWithFlags[i] >> GnssStatus.SVID_SHIFT_WIDTH; + // Other satellites passed through these APIs before GnssSvStatus was availble. + // GPS, SBAS & QZSS can pass through at their nominally + // assigned prn number (as long as it fits in the valid 0-255 range below.) + // Glonass, and Beidou are passed through with the defacto standard offsets + // Other future constellation reporting (e.g. Galileo) needs to use + // GnssSvStatus on (N level) HAL & Java layers. + if (constellationType == GnssStatus.CONSTELLATION_GLONASS) { + prn += GLONASS_SVID_OFFSET; + } else if (constellationType == GnssStatus.CONSTELLATION_BEIDOU) { + prn += BEIDOU_SVID_OFFSET; + } else if (constellationType == GnssStatus.CONSTELLATION_SBAS) { + prn += SBAS_SVID_OFFSET; + } else if ((constellationType != GnssStatus.CONSTELLATION_GPS) && + (constellationType != GnssStatus.CONSTELLATION_QZSS)) { + continue; + } + if (prn > 0 && prn <= NUM_SATELLITES) { + GpsSatellite satellite = mSatellites.get(prn); + if (satellite == null) { + satellite = new GpsSatellite(prn); + mSatellites.put(prn, satellite); + } + + satellite.mValid = true; + satellite.mSnr = cn0s[i]; + satellite.mElevation = elevations[i]; + satellite.mAzimuth = azimuths[i]; + satellite.mHasEphemeris = + (svidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) != 0; + satellite.mHasAlmanac = + (svidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) != 0; + satellite.mUsedInFix = + (svidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0; + } + } + } + + /** + * Copies GPS satellites information from GnssStatus object. + * Since this method is only used within {@link LocationManager#getGpsStatus}, + * it does not need to be synchronized. + * @hide + */ + void setStatus(GnssStatus status, int timeToFirstFix) { + mTimeToFirstFix = timeToFirstFix; + setStatus(status.mSvCount, status.mSvidWithFlags, status.mCn0DbHz, status.mElevations, + status.mAzimuths); + } + + void setTimeToFirstFix(int ttff) { + mTimeToFirstFix = ttff; + } + + /** + * Returns the time required to receive the first fix since the most recent + * restart of the GPS engine. + * + * @return time to first fix in milliseconds + */ + public int getTimeToFirstFix() { + return mTimeToFirstFix; + } + + /** + * Returns an array of {@link GpsSatellite} objects, which represent the + * current state of the GPS engine. + * + * @return the list of satellites + */ + public Iterable<GpsSatellite> getSatellites() { + return mSatelliteList; + } + + /** + * Returns the maximum number of satellites that can be in the satellite + * list that can be returned by {@link #getSatellites()}. + * + * @return the maximum number of satellites + */ + public int getMaxSatellites() { + return NUM_SATELLITES; + } + + private void clearSatellites() { + int satellitesCount = mSatellites.size(); + for (int i = 0; i < satellitesCount; i++) { + GpsSatellite satellite = mSatellites.valueAt(i); + satellite.mValid = false; + } + } +} diff --git a/android/location/LocalListenerHelper.java b/android/location/LocalListenerHelper.java new file mode 100644 index 00000000..d7d2c513 --- /dev/null +++ b/android/location/LocalListenerHelper.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 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 com.android.internal.util.Preconditions; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * A base handler class to manage transport and local listeners. + * + * @hide + */ +abstract class LocalListenerHelper<TListener> { + private final HashMap<TListener, Handler> mListeners = new HashMap<>(); + + private final String mTag; + private final Context mContext; + + protected LocalListenerHelper(Context context, String name) { + Preconditions.checkNotNull(name); + mContext = context; + mTag = name; + } + + public boolean add(@NonNull TListener listener, Handler handler) { + Preconditions.checkNotNull(listener); + synchronized (mListeners) { + // we need to register with the service first, because we need to find out if the + // service will actually support the request before we attempt anything + if (mListeners.isEmpty()) { + boolean registeredWithService; + try { + registeredWithService = registerWithServer(); + } catch (RemoteException e) { + Log.e(mTag, "Error handling first listener.", e); + return false; + } + if (!registeredWithService) { + Log.e(mTag, "Unable to register listener transport."); + return false; + } + } + if (mListeners.containsKey(listener)) { + return true; + } + mListeners.put(listener, handler); + return true; + } + } + + public void remove(@NonNull TListener listener) { + Preconditions.checkNotNull(listener); + synchronized (mListeners) { + boolean removed = mListeners.containsKey(listener); + mListeners.remove(listener); + boolean isLastRemoved = removed && mListeners.isEmpty(); + if (isLastRemoved) { + try { + unregisterFromServer(); + } catch (RemoteException e) { + Log.v(mTag, "Error handling last listener removal", e); + } + } + } + } + + protected abstract boolean registerWithServer() throws RemoteException; + protected abstract void unregisterFromServer() throws RemoteException; + + protected interface ListenerOperation<TListener> { + void execute(TListener listener) throws RemoteException; + } + + protected Context getContext() { + return mContext; + } + + private void executeOperation(ListenerOperation<TListener> operation, TListener listener) { + try { + operation.execute(listener); + } catch (RemoteException e) { + Log.e(mTag, "Error in monitored listener.", e); + // don't return, give a fair chance to all listeners to receive the event + } + } + + protected void foreach(final ListenerOperation<TListener> operation) { + Collection<Map.Entry<TListener, Handler>> listeners; + synchronized (mListeners) { + listeners = new ArrayList<>(mListeners.entrySet()); + } + for (final Map.Entry<TListener, Handler> listener : listeners) { + if (listener.getValue() == null) { + executeOperation(operation, listener.getKey()); + } else { + listener.getValue().post(new Runnable() { + @Override + public void run() { + executeOperation(operation, listener.getKey()); + } + }); + } + } + } +} diff --git a/android/location/Location.java b/android/location/Location.java new file mode 100644 index 00000000..e8eaa59a --- /dev/null +++ b/android/location/Location.java @@ -0,0 +1,1192 @@ +/* + * Copyright (C) 2007 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.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.Printer; +import android.util.TimeUtils; + +import java.text.DecimalFormat; +import java.util.StringTokenizer; + +/** + * A data class representing a geographic location. + * + * <p>A location can consist of a latitude, longitude, timestamp, + * and other information such as bearing, altitude and velocity. + * + * <p>All locations generated by the {@link LocationManager} are + * guaranteed to have a valid latitude, longitude, and timestamp + * (both UTC time and elapsed real-time since boot), all other + * parameters are optional. + */ +public class Location implements Parcelable { + /** + * Constant used to specify formatting of a latitude or longitude + * in the form "[+-]DDD.DDDDD where D indicates degrees. + */ + public static final int FORMAT_DEGREES = 0; + + /** + * Constant used to specify formatting of a latitude or longitude + * in the form "[+-]DDD:MM.MMMMM" where D indicates degrees and + * M indicates minutes of arc (1 minute = 1/60th of a degree). + */ + public static final int FORMAT_MINUTES = 1; + + /** + * Constant used to specify formatting of a latitude or longitude + * in the form "DDD:MM:SS.SSSSS" where D indicates degrees, M + * indicates minutes of arc, and S indicates seconds of arc (1 + * minute = 1/60th of a degree, 1 second = 1/3600th of a degree). + */ + public static final int FORMAT_SECONDS = 2; + + /** + * Bundle key for a version of the location that has been fed through + * LocationFudger. Allows location providers to flag locations as being + * safe for use with ACCESS_COARSE_LOCATION permission. + * + * @hide + */ + public static final String EXTRA_COARSE_LOCATION = "coarseLocation"; + + /** + * Bundle key for a version of the location containing no GPS data. + * Allows location providers to flag locations as being safe to + * feed to LocationFudger. + * + * @hide + */ + public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; + + /** + * Bit mask for mFieldsMask indicating the presence of mAltitude. + */ + private static final int HAS_ALTITUDE_MASK = 1; + /** + * Bit mask for mFieldsMask indicating the presence of mSpeed. + */ + private static final int HAS_SPEED_MASK = 2; + /** + * Bit mask for mFieldsMask indicating the presence of mBearing. + */ + private static final int HAS_BEARING_MASK = 4; + /** + * Bit mask for mFieldsMask indicating the presence of mHorizontalAccuracy. + */ + private static final int HAS_HORIZONTAL_ACCURACY_MASK = 8; + /** + * Bit mask for mFieldsMask indicating location is from a mock provider. + */ + private static final int HAS_MOCK_PROVIDER_MASK = 16; + /** + * Bit mask for mFieldsMask indicating the presence of mVerticalAccuracy. + */ + private static final int HAS_VERTICAL_ACCURACY_MASK = 32; + /** + * Bit mask for mFieldsMask indicating the presence of mSpeedAccuracy. + */ + private static final int HAS_SPEED_ACCURACY_MASK = 64; + /** + * Bit mask for mFieldsMask indicating the presence of mBearingAccuracy. + */ + private static final int HAS_BEARING_ACCURACY_MASK = 128; + + // Cached data to make bearing/distance computations more efficient for the case + // where distanceTo and bearingTo are called in sequence. Assume this typically happens + // on the same thread for caching purposes. + private static ThreadLocal<BearingDistanceCache> sBearingDistanceCache + = new ThreadLocal<BearingDistanceCache>() { + @Override + protected BearingDistanceCache initialValue() { + return new BearingDistanceCache(); + } + }; + + private String mProvider; + private long mTime = 0; + private long mElapsedRealtimeNanos = 0; + private double mLatitude = 0.0; + private double mLongitude = 0.0; + private double mAltitude = 0.0f; + private float mSpeed = 0.0f; + private float mBearing = 0.0f; + private float mHorizontalAccuracyMeters = 0.0f; + private float mVerticalAccuracyMeters = 0.0f; + private float mSpeedAccuracyMetersPerSecond = 0.0f; + private float mBearingAccuracyDegrees = 0.0f; + + private Bundle mExtras = null; + + // A bitmask of fields present in this object (see HAS_* constants defined above). + private byte mFieldsMask = 0; + + /** + * Construct a new Location with a named provider. + * + * <p>By default time, latitude and longitude are 0, and the location + * has no bearing, altitude, speed, accuracy or extras. + * + * @param provider the name of the provider that generated this location + */ + public Location(String provider) { + mProvider = provider; + } + + /** + * Construct a new Location object that is copied from an existing one. + */ + public Location(Location l) { + set(l); + } + + /** + * Sets the contents of the location to the values from the given location. + */ + public void set(Location l) { + mProvider = l.mProvider; + mTime = l.mTime; + mElapsedRealtimeNanos = l.mElapsedRealtimeNanos; + mFieldsMask = l.mFieldsMask; + mLatitude = l.mLatitude; + mLongitude = l.mLongitude; + mAltitude = l.mAltitude; + mSpeed = l.mSpeed; + mBearing = l.mBearing; + mHorizontalAccuracyMeters = l.mHorizontalAccuracyMeters; + mVerticalAccuracyMeters = l.mVerticalAccuracyMeters; + mSpeedAccuracyMetersPerSecond = l.mSpeedAccuracyMetersPerSecond; + mBearingAccuracyDegrees = l.mBearingAccuracyDegrees; + mExtras = (l.mExtras == null) ? null : new Bundle(l.mExtras); + } + + /** + * Clears the contents of the location. + */ + public void reset() { + mProvider = null; + mTime = 0; + mElapsedRealtimeNanos = 0; + mFieldsMask = 0; + mLatitude = 0; + mLongitude = 0; + mAltitude = 0; + mSpeed = 0; + mBearing = 0; + mHorizontalAccuracyMeters = 0; + mVerticalAccuracyMeters = 0; + mSpeedAccuracyMetersPerSecond = 0; + mBearingAccuracyDegrees = 0; + mExtras = null; + } + + /** + * Converts a coordinate to a String representation. The outputType + * may be one of FORMAT_DEGREES, FORMAT_MINUTES, or FORMAT_SECONDS. + * The coordinate must be a valid double between -180.0 and 180.0. + * This conversion is performed in a method that is dependent on the + * default locale, and so is not guaranteed to round-trip with + * {@link #convert(String)}. + * + * @throws IllegalArgumentException if coordinate is less than + * -180.0, greater than 180.0, or is not a number. + * @throws IllegalArgumentException if outputType is not one of + * FORMAT_DEGREES, FORMAT_MINUTES, or FORMAT_SECONDS. + */ + public static String convert(double coordinate, int outputType) { + if (coordinate < -180.0 || coordinate > 180.0 || + Double.isNaN(coordinate)) { + throw new IllegalArgumentException("coordinate=" + coordinate); + } + if ((outputType != FORMAT_DEGREES) && + (outputType != FORMAT_MINUTES) && + (outputType != FORMAT_SECONDS)) { + throw new IllegalArgumentException("outputType=" + outputType); + } + + StringBuilder sb = new StringBuilder(); + + // Handle negative values + if (coordinate < 0) { + sb.append('-'); + coordinate = -coordinate; + } + + DecimalFormat df = new DecimalFormat("###.#####"); + if (outputType == FORMAT_MINUTES || outputType == FORMAT_SECONDS) { + int degrees = (int) Math.floor(coordinate); + sb.append(degrees); + sb.append(':'); + coordinate -= degrees; + coordinate *= 60.0; + if (outputType == FORMAT_SECONDS) { + int minutes = (int) Math.floor(coordinate); + sb.append(minutes); + sb.append(':'); + coordinate -= minutes; + coordinate *= 60.0; + } + } + sb.append(df.format(coordinate)); + return sb.toString(); + } + + /** + * Converts a String in one of the formats described by + * FORMAT_DEGREES, FORMAT_MINUTES, or FORMAT_SECONDS into a + * double. This conversion is performed in a locale agnostic + * method, and so is not guaranteed to round-trip with + * {@link #convert(double, int)}. + * + * @throws NullPointerException if coordinate is null + * @throws IllegalArgumentException if the coordinate is not + * in one of the valid formats. + */ + public static double convert(String coordinate) { + // IllegalArgumentException if bad syntax + if (coordinate == null) { + throw new NullPointerException("coordinate"); + } + + boolean negative = false; + if (coordinate.charAt(0) == '-') { + coordinate = coordinate.substring(1); + negative = true; + } + + StringTokenizer st = new StringTokenizer(coordinate, ":"); + int tokens = st.countTokens(); + if (tokens < 1) { + throw new IllegalArgumentException("coordinate=" + coordinate); + } + try { + String degrees = st.nextToken(); + double val; + if (tokens == 1) { + val = Double.parseDouble(degrees); + return negative ? -val : val; + } + + String minutes = st.nextToken(); + int deg = Integer.parseInt(degrees); + double min; + double sec = 0.0; + boolean secPresent = false; + + if (st.hasMoreTokens()) { + min = Integer.parseInt(minutes); + String seconds = st.nextToken(); + sec = Double.parseDouble(seconds); + secPresent = true; + } else { + min = Double.parseDouble(minutes); + } + + boolean isNegative180 = negative && (deg == 180) && + (min == 0) && (sec == 0); + + // deg must be in [0, 179] except for the case of -180 degrees + if ((deg < 0.0) || (deg > 179 && !isNegative180)) { + throw new IllegalArgumentException("coordinate=" + coordinate); + } + + // min must be in [0, 59] if seconds are present, otherwise [0.0, 60.0) + if (min < 0 || min >= 60 || (secPresent && (min > 59))) { + throw new IllegalArgumentException("coordinate=" + + coordinate); + } + + // sec must be in [0.0, 60.0) + if (sec < 0 || sec >= 60) { + throw new IllegalArgumentException("coordinate=" + + coordinate); + } + + val = deg*3600.0 + min*60.0 + sec; + val /= 3600.0; + return negative ? -val : val; + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("coordinate=" + coordinate); + } + } + + private static void computeDistanceAndBearing(double lat1, double lon1, + double lat2, double lon2, BearingDistanceCache results) { + // Based on http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf + // using the "Inverse Formula" (section 4) + + int MAXITERS = 20; + // Convert lat/long to radians + lat1 *= Math.PI / 180.0; + lat2 *= Math.PI / 180.0; + lon1 *= Math.PI / 180.0; + lon2 *= Math.PI / 180.0; + + double a = 6378137.0; // WGS84 major axis + double b = 6356752.3142; // WGS84 semi-major axis + double f = (a - b) / a; + double aSqMinusBSqOverBSq = (a * a - b * b) / (b * b); + + double L = lon2 - lon1; + double A = 0.0; + double U1 = Math.atan((1.0 - f) * Math.tan(lat1)); + double U2 = Math.atan((1.0 - f) * Math.tan(lat2)); + + double cosU1 = Math.cos(U1); + double cosU2 = Math.cos(U2); + double sinU1 = Math.sin(U1); + double sinU2 = Math.sin(U2); + double cosU1cosU2 = cosU1 * cosU2; + double sinU1sinU2 = sinU1 * sinU2; + + double sigma = 0.0; + double deltaSigma = 0.0; + double cosSqAlpha = 0.0; + double cos2SM = 0.0; + double cosSigma = 0.0; + double sinSigma = 0.0; + double cosLambda = 0.0; + double sinLambda = 0.0; + + double lambda = L; // initial guess + for (int iter = 0; iter < MAXITERS; iter++) { + double lambdaOrig = lambda; + cosLambda = Math.cos(lambda); + sinLambda = Math.sin(lambda); + double t1 = cosU2 * sinLambda; + double t2 = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda; + double sinSqSigma = t1 * t1 + t2 * t2; // (14) + sinSigma = Math.sqrt(sinSqSigma); + cosSigma = sinU1sinU2 + cosU1cosU2 * cosLambda; // (15) + sigma = Math.atan2(sinSigma, cosSigma); // (16) + double sinAlpha = (sinSigma == 0) ? 0.0 : + cosU1cosU2 * sinLambda / sinSigma; // (17) + cosSqAlpha = 1.0 - sinAlpha * sinAlpha; + cos2SM = (cosSqAlpha == 0) ? 0.0 : + cosSigma - 2.0 * sinU1sinU2 / cosSqAlpha; // (18) + + double uSquared = cosSqAlpha * aSqMinusBSqOverBSq; // defn + A = 1 + (uSquared / 16384.0) * // (3) + (4096.0 + uSquared * + (-768 + uSquared * (320.0 - 175.0 * uSquared))); + double B = (uSquared / 1024.0) * // (4) + (256.0 + uSquared * + (-128.0 + uSquared * (74.0 - 47.0 * uSquared))); + double C = (f / 16.0) * + cosSqAlpha * + (4.0 + f * (4.0 - 3.0 * cosSqAlpha)); // (10) + double cos2SMSq = cos2SM * cos2SM; + deltaSigma = B * sinSigma * // (6) + (cos2SM + (B / 4.0) * + (cosSigma * (-1.0 + 2.0 * cos2SMSq) - + (B / 6.0) * cos2SM * + (-3.0 + 4.0 * sinSigma * sinSigma) * + (-3.0 + 4.0 * cos2SMSq))); + + lambda = L + + (1.0 - C) * f * sinAlpha * + (sigma + C * sinSigma * + (cos2SM + C * cosSigma * + (-1.0 + 2.0 * cos2SM * cos2SM))); // (11) + + double delta = (lambda - lambdaOrig) / lambda; + if (Math.abs(delta) < 1.0e-12) { + break; + } + } + + float distance = (float) (b * A * (sigma - deltaSigma)); + results.mDistance = distance; + float initialBearing = (float) Math.atan2(cosU2 * sinLambda, + cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); + initialBearing *= 180.0 / Math.PI; + results.mInitialBearing = initialBearing; + float finalBearing = (float) Math.atan2(cosU1 * sinLambda, + -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda); + finalBearing *= 180.0 / Math.PI; + results.mFinalBearing = finalBearing; + results.mLat1 = lat1; + results.mLat2 = lat2; + results.mLon1 = lon1; + results.mLon2 = lon2; + } + + /** + * Computes the approximate distance in meters between two + * locations, and optionally the initial and final bearings of the + * shortest path between them. Distance and bearing are defined using the + * WGS84 ellipsoid. + * + * <p> The computed distance is stored in results[0]. If results has length + * 2 or greater, the initial bearing is stored in results[1]. If results has + * length 3 or greater, the final bearing is stored in results[2]. + * + * @param startLatitude the starting latitude + * @param startLongitude the starting longitude + * @param endLatitude the ending latitude + * @param endLongitude the ending longitude + * @param results an array of floats to hold the results + * + * @throws IllegalArgumentException if results is null or has length < 1 + */ + public static void distanceBetween(double startLatitude, double startLongitude, + double endLatitude, double endLongitude, float[] results) { + if (results == null || results.length < 1) { + throw new IllegalArgumentException("results is null or has length < 1"); + } + BearingDistanceCache cache = sBearingDistanceCache.get(); + computeDistanceAndBearing(startLatitude, startLongitude, + endLatitude, endLongitude, cache); + results[0] = cache.mDistance; + if (results.length > 1) { + results[1] = cache.mInitialBearing; + if (results.length > 2) { + results[2] = cache.mFinalBearing; + } + } + } + + /** + * Returns the approximate distance in meters between this + * location and the given location. Distance is defined using + * the WGS84 ellipsoid. + * + * @param dest the destination location + * @return the approximate distance in meters + */ + public float distanceTo(Location dest) { + BearingDistanceCache cache = sBearingDistanceCache.get(); + // See if we already have the result + if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 || + dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) { + computeDistanceAndBearing(mLatitude, mLongitude, + dest.mLatitude, dest.mLongitude, cache); + } + return cache.mDistance; + } + + /** + * Returns the approximate initial bearing in degrees East of true + * North when traveling along the shortest path between this + * location and the given location. The shortest path is defined + * using the WGS84 ellipsoid. Locations that are (nearly) + * antipodal may produce meaningless results. + * + * @param dest the destination location + * @return the initial bearing in degrees + */ + public float bearingTo(Location dest) { + BearingDistanceCache cache = sBearingDistanceCache.get(); + // See if we already have the result + if (mLatitude != cache.mLat1 || mLongitude != cache.mLon1 || + dest.mLatitude != cache.mLat2 || dest.mLongitude != cache.mLon2) { + computeDistanceAndBearing(mLatitude, mLongitude, + dest.mLatitude, dest.mLongitude, cache); + } + return cache.mInitialBearing; + } + + /** + * Returns the name of the provider that generated this fix. + * + * @return the provider, or null if it has not been set + */ + public String getProvider() { + return mProvider; + } + + /** + * Sets the name of the provider that generated this fix. + */ + public void setProvider(String provider) { + mProvider = provider; + } + + /** + * Return the UTC time of this fix, in milliseconds since January 1, 1970. + * + * <p>Note that the UTC time on a device is not monotonic: it + * can jump forwards or backwards unpredictably. So always use + * {@link #getElapsedRealtimeNanos} when calculating time deltas. + * + * <p>On the other hand, {@link #getTime} is useful for presenting + * a human readable time to the user, or for carefully comparing + * location fixes across reboot or across devices. + * + * <p>All locations generated by the {@link LocationManager} + * are guaranteed to have a valid UTC time, however remember that + * the system time may have changed since the location was generated. + * + * @return time of fix, in milliseconds since January 1, 1970. + */ + public long getTime() { + return mTime; + } + + /** + * Set the UTC time of this fix, in milliseconds since January 1, + * 1970. + * + * @param time UTC time of this fix, in milliseconds since January 1, 1970 + */ + public void setTime(long time) { + mTime = time; + } + + /** + * Return the time of this fix, in elapsed real-time since system boot. + * + * <p>This value can be reliably compared to + * {@link android.os.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 {@link #getTime}. + * + * <p>All locations generated by the {@link LocationManager} + * are guaranteed to have a valid elapsed real-time. + * + * @return elapsed real-time of fix, in nanoseconds since system boot. + */ + public long getElapsedRealtimeNanos() { + return mElapsedRealtimeNanos; + } + + /** + * Set the time of this fix, in elapsed real-time since system boot. + * + * @param time elapsed real-time of fix, in nanoseconds since system boot. + */ + public void setElapsedRealtimeNanos(long time) { + mElapsedRealtimeNanos = time; + } + + /** + * Get the latitude, in degrees. + * + * <p>All locations generated by the {@link LocationManager} + * will have a valid latitude. + */ + public double getLatitude() { + return mLatitude; + } + + /** + * Set the latitude, in degrees. + */ + public void setLatitude(double latitude) { + mLatitude = latitude; + } + + /** + * Get the longitude, in degrees. + * + * <p>All locations generated by the {@link LocationManager} + * will have a valid longitude. + */ + public double getLongitude() { + return mLongitude; + } + + /** + * Set the longitude, in degrees. + */ + public void setLongitude(double longitude) { + mLongitude = longitude; + } + + /** + * True if this location has an altitude. + */ + public boolean hasAltitude() { + return (mFieldsMask & HAS_ALTITUDE_MASK) != 0; + } + + /** + * Get the altitude if available, in meters above the WGS 84 reference + * ellipsoid. + * + * <p>If this location does not have an altitude then 0.0 is returned. + */ + public double getAltitude() { + return mAltitude; + } + + /** + * Set the altitude, in meters above the WGS 84 reference ellipsoid. + * + * <p>Following this call {@link #hasAltitude} will return true. + */ + public void setAltitude(double altitude) { + mAltitude = altitude; + mFieldsMask |= HAS_ALTITUDE_MASK; + } + + /** + * Remove the altitude from this location. + * + * <p>Following this call {@link #hasAltitude} will return false, + * and {@link #getAltitude} will return 0.0. + * + * @deprecated use a new Location object for location updates. + */ + @Deprecated + public void removeAltitude() { + mAltitude = 0.0f; + mFieldsMask &= ~HAS_ALTITUDE_MASK; + } + + /** + * True if this location has a speed. + */ + public boolean hasSpeed() { + return (mFieldsMask & HAS_SPEED_MASK) != 0; + } + + /** + * Get the speed if it is available, in meters/second over ground. + * + * <p>If this location does not have a speed then 0.0 is returned. + */ + public float getSpeed() { + return mSpeed; + } + + /** + * Set the speed, in meters/second over ground. + * + * <p>Following this call {@link #hasSpeed} will return true. + */ + public void setSpeed(float speed) { + mSpeed = speed; + mFieldsMask |= HAS_SPEED_MASK; + } + + /** + * Remove the speed from this location. + * + * <p>Following this call {@link #hasSpeed} will return false, + * and {@link #getSpeed} will return 0.0. + * + * @deprecated use a new Location object for location updates. + */ + @Deprecated + public void removeSpeed() { + mSpeed = 0.0f; + mFieldsMask &= ~HAS_SPEED_MASK; + } + + /** + * True if this location has a bearing. + */ + public boolean hasBearing() { + return (mFieldsMask & HAS_BEARING_MASK) != 0; + } + + /** + * Get the bearing, in degrees. + * + * <p>Bearing is the horizontal direction of travel of this device, + * and is not related to the device orientation. It is guaranteed to + * be in the range (0.0, 360.0] if the device has a bearing. + * + * <p>If this location does not have a bearing then 0.0 is returned. + */ + public float getBearing() { + return mBearing; + } + + /** + * Set the bearing, in degrees. + * + * <p>Bearing is the horizontal direction of travel of this device, + * and is not related to the device orientation. + * + * <p>The input will be wrapped into the range (0.0, 360.0]. + */ + public void setBearing(float bearing) { + while (bearing < 0.0f) { + bearing += 360.0f; + } + while (bearing >= 360.0f) { + bearing -= 360.0f; + } + mBearing = bearing; + mFieldsMask |= HAS_BEARING_MASK; + } + + /** + * Remove the bearing from this location. + * + * <p>Following this call {@link #hasBearing} will return false, + * and {@link #getBearing} will return 0.0. + * + * @deprecated use a new Location object for location updates. + */ + @Deprecated + public void removeBearing() { + mBearing = 0.0f; + mFieldsMask &= ~HAS_BEARING_MASK; + } + + /** + * True if this location has a horizontal accuracy. + * + * <p>All locations generated by the {@link LocationManager} have an horizontal accuracy. + */ + public boolean hasAccuracy() { + return (mFieldsMask & HAS_HORIZONTAL_ACCURACY_MASK) != 0; + } + + /** + * Get the estimated horizontal accuracy of this location, radial, in meters. + * + * <p>We define horizontal accuracy as the radius of 68% confidence. In other + * words, if you draw a circle centered at this location's + * latitude and longitude, and with a radius equal to the accuracy, + * then there is a 68% probability that the true location is inside + * the circle. + * + * <p>This accuracy estimation is only concerned with horizontal + * accuracy, and does not indicate the accuracy of bearing, + * velocity or altitude if those are included in this Location. + * + * <p>If this location does not have a horizontal accuracy, then 0.0 is returned. + * All locations generated by the {@link LocationManager} include horizontal accuracy. + */ + public float getAccuracy() { + return mHorizontalAccuracyMeters; + } + + /** + * Set the estimated horizontal accuracy of this location, meters. + * + * <p>See {@link #getAccuracy} for the definition of horizontal accuracy. + * + * <p>Following this call {@link #hasAccuracy} will return true. + */ + public void setAccuracy(float horizontalAccuracy) { + mHorizontalAccuracyMeters = horizontalAccuracy; + mFieldsMask |= HAS_HORIZONTAL_ACCURACY_MASK; + } + + /** + * Remove the horizontal accuracy from this location. + * + * <p>Following this call {@link #hasAccuracy} will return false, and + * {@link #getAccuracy} will return 0.0. + * + * @deprecated use a new Location object for location updates. + */ + @Deprecated + public void removeAccuracy() { + mHorizontalAccuracyMeters = 0.0f; + mFieldsMask &= ~HAS_HORIZONTAL_ACCURACY_MASK; + } + + /** + * True if this location has a vertical accuracy. + */ + public boolean hasVerticalAccuracy() { + return (mFieldsMask & HAS_VERTICAL_ACCURACY_MASK) != 0; + } + + /** + * Get the estimated vertical accuracy of this location, in meters. + * + * <p>We define vertical accuracy as the radius of 68% confidence. In other + * words, if you draw a circle centered at this location's altitude, and with a radius + * equal to the vertical accuracy, then there is a 68% probability that the true altitude is + * inside the circle. + * + * <p>In statistical terms, it is assumed that location errors + * are random with a normal distribution, so the 68% confidence circle + * represents one standard deviation. Note that in practice, location + * errors do not always follow such a simple distribution. + * + * <p>If this location does not have a vertical accuracy, then 0.0 is returned. + */ + public float getVerticalAccuracyMeters() { + return mVerticalAccuracyMeters; + } + + /** + * Set the estimated vertical accuracy of this location, meters. + * + * <p>See {@link #getVerticalAccuracyMeters} for the definition of vertical accuracy. + * + * <p>Following this call {@link #hasVerticalAccuracy} will return true. + */ + public void setVerticalAccuracyMeters(float verticalAccuracyMeters) { + mVerticalAccuracyMeters = verticalAccuracyMeters; + mFieldsMask |= HAS_VERTICAL_ACCURACY_MASK; + } + + /** + * Remove the vertical accuracy from this location. + * + * <p>Following this call {@link #hasVerticalAccuracy} will return false, and + * {@link #getVerticalAccuracyMeters} will return 0.0. + * + * @deprecated use a new Location object for location updates. + * @removed + */ + @Deprecated + public void removeVerticalAccuracy() { + mVerticalAccuracyMeters = 0.0f; + mFieldsMask &= ~HAS_VERTICAL_ACCURACY_MASK; + } + + /** + * True if this location has a speed accuracy. + */ + public boolean hasSpeedAccuracy() { + return (mFieldsMask & HAS_SPEED_ACCURACY_MASK) != 0; + } + + /** + * Get the estimated speed accuracy of this location, in meters per second. + * + * <p>We define speed accuracy as a 1-standard-deviation value, i.e. as 1-side of the + * 2-sided range above and below the estimated + * speed reported by {@link #getSpeed()}, within which there is a 68% probability of + * finding the true speed. + * + * <p>For example, if {@link #getSpeed()} returns 5.0, and + * {@link #getSpeedAccuracyMetersPerSecond()} returns 1.0, then there is a 68% probably of the + * true speed being between 4.0 and 6.0 meters per second. + * + * <p>Note that the speed and speed accuracy is often better than would be obtained simply from + * differencing sequential positions, such as when the Doppler measurements from GNSS satellites + * are used. + * + * <p>If this location does not have a speed accuracy, then 0.0 is returned. + */ + public float getSpeedAccuracyMetersPerSecond() { + return mSpeedAccuracyMetersPerSecond; + } + + /** + * Set the estimated speed accuracy of this location, meters per second. + * + * <p>See {@link #getSpeedAccuracyMetersPerSecond} for the definition of speed accuracy. + * + * <p>Following this call {@link #hasSpeedAccuracy} will return true. + */ + public void setSpeedAccuracyMetersPerSecond(float speedAccuracyMeterPerSecond) { + mSpeedAccuracyMetersPerSecond = speedAccuracyMeterPerSecond; + mFieldsMask |= HAS_SPEED_ACCURACY_MASK; + } + + /** + * Remove the speed accuracy from this location. + * + * <p>Following this call {@link #hasSpeedAccuracy} will return false, and + * {@link #getSpeedAccuracyMetersPerSecond} will return 0.0. + * + * @deprecated use a new Location object for location updates. + * @removed + */ + @Deprecated + public void removeSpeedAccuracy() { + mSpeedAccuracyMetersPerSecond = 0.0f; + mFieldsMask &= ~HAS_SPEED_ACCURACY_MASK; + } + + /** + * True if this location has a bearing accuracy. + */ + public boolean hasBearingAccuracy() { + return (mFieldsMask & HAS_BEARING_ACCURACY_MASK) != 0; + } + + /** + * Get the estimated bearing accuracy of this location, in degrees. + * + * <p>We define bearing accuracy as a 1-standard-deviation value, i.e. as 1-side of the + * 2-sided range on each side of the estimated bearing reported by {@link #getBearing()}, + * within which there is a 68% probability of finding the true bearing. + * + * <p>For example, if {@link #getBearing()} returns 60., and + * {@link #getBearingAccuracyDegrees()} ()} returns 10., then there is a 68% probably of the + * true bearing being between 50. and 70. degrees. + * + * <p>If this location does not have a bearing accuracy, then 0.0 is returned. + */ + public float getBearingAccuracyDegrees() { + return mBearingAccuracyDegrees; + } + + /** + * Set the estimated bearing accuracy of this location, degrees. + * + * <p>See {@link #getBearingAccuracyDegrees} for the definition of bearing accuracy. + * + * <p>Following this call {@link #hasBearingAccuracy} will return true. + */ + public void setBearingAccuracyDegrees(float bearingAccuracyDegrees) { + mBearingAccuracyDegrees = bearingAccuracyDegrees; + mFieldsMask |= HAS_BEARING_ACCURACY_MASK; + } + + /** + * Remove the bearing accuracy from this location. + * + * <p>Following this call {@link #hasBearingAccuracy} will return false, and + * {@link #getBearingAccuracyDegrees} will return 0.0. + * + * @deprecated use a new Location object for location updates. + * @removed + */ + @Deprecated + public void removeBearingAccuracy() { + mBearingAccuracyDegrees = 0.0f; + mFieldsMask &= ~HAS_BEARING_ACCURACY_MASK; + } + + /** + * Return true if this Location object is complete. + * + * <p>A location object is currently considered complete if it has + * a valid provider, accuracy, wall-clock time and elapsed real-time. + * + * <p>All locations supplied by the {@link LocationManager} to + * applications must be complete. + * + * @see #makeComplete + * @hide + */ + @SystemApi + public boolean isComplete() { + if (mProvider == null) return false; + if (!hasAccuracy()) return false; + if (mTime == 0) return false; + if (mElapsedRealtimeNanos == 0) return false; + return true; + } + + /** + * Helper to fill incomplete fields. + * + * <p>Used to assist in backwards compatibility with + * Location objects received from applications. + * + * @see #isComplete + * @hide + */ + @SystemApi + public void makeComplete() { + if (mProvider == null) mProvider = "?"; + if (!hasAccuracy()) { + mFieldsMask |= HAS_HORIZONTAL_ACCURACY_MASK; + mHorizontalAccuracyMeters = 100.0f; + } + if (mTime == 0) mTime = System.currentTimeMillis(); + if (mElapsedRealtimeNanos == 0) mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); + } + + /** + * Returns additional provider-specific information about the + * location fix as a Bundle. The keys and values are determined + * by the provider. If no additional information is available, + * null is returned. + * + * <p> A number of common key/value pairs are listed + * below. Providers that use any of the keys on this list must + * provide the corresponding value as described below. + * + * <ul> + * <li> satellites - the number of satellites used to derive the fix + * </ul> + */ + public Bundle getExtras() { + return mExtras; + } + + /** + * Sets the extra information associated with this fix to the + * given Bundle. + */ + public void setExtras(Bundle extras) { + mExtras = (extras == null) ? null : new Bundle(extras); + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Location["); + s.append(mProvider); + s.append(String.format(" %.6f,%.6f", mLatitude, mLongitude)); + if (hasAccuracy()) s.append(String.format(" hAcc=%.0f", mHorizontalAccuracyMeters)); + else s.append(" hAcc=???"); + if (mTime == 0) { + s.append(" t=?!?"); + } + if (mElapsedRealtimeNanos == 0) { + s.append(" et=?!?"); + } else { + s.append(" et="); + TimeUtils.formatDuration(mElapsedRealtimeNanos / 1000000L, s); + } + if (hasAltitude()) s.append(" alt=").append(mAltitude); + if (hasSpeed()) s.append(" vel=").append(mSpeed); + if (hasBearing()) s.append(" bear=").append(mBearing); + if (hasVerticalAccuracy()) s.append(String.format(" vAcc=%.0f", mVerticalAccuracyMeters)); + else s.append(" vAcc=???"); + if (hasSpeedAccuracy()) s.append(String.format(" sAcc=%.0f", mSpeedAccuracyMetersPerSecond)); + else s.append(" sAcc=???"); + if (hasBearingAccuracy()) s.append(String.format(" bAcc=%.0f", mBearingAccuracyDegrees)); + else s.append(" bAcc=???"); + if (isFromMockProvider()) s.append(" mock"); + + if (mExtras != null) { + s.append(" {").append(mExtras).append('}'); + } + s.append(']'); + return s.toString(); + } + + public void dump(Printer pw, String prefix) { + pw.println(prefix + toString()); + } + + public static final Parcelable.Creator<Location> CREATOR = + new Parcelable.Creator<Location>() { + @Override + public Location createFromParcel(Parcel in) { + String provider = in.readString(); + Location l = new Location(provider); + l.mTime = in.readLong(); + l.mElapsedRealtimeNanos = in.readLong(); + l.mFieldsMask = in.readByte(); + l.mLatitude = in.readDouble(); + l.mLongitude = in.readDouble(); + l.mAltitude = in.readDouble(); + l.mSpeed = in.readFloat(); + l.mBearing = in.readFloat(); + l.mHorizontalAccuracyMeters = in.readFloat(); + l.mVerticalAccuracyMeters = in.readFloat(); + l.mSpeedAccuracyMetersPerSecond = in.readFloat(); + l.mBearingAccuracyDegrees = in.readFloat(); + l.mExtras = Bundle.setDefusable(in.readBundle(), true); + return l; + } + + @Override + public Location[] newArray(int size) { + return new Location[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mProvider); + parcel.writeLong(mTime); + parcel.writeLong(mElapsedRealtimeNanos); + parcel.writeByte(mFieldsMask); + parcel.writeDouble(mLatitude); + parcel.writeDouble(mLongitude); + parcel.writeDouble(mAltitude); + parcel.writeFloat(mSpeed); + parcel.writeFloat(mBearing); + parcel.writeFloat(mHorizontalAccuracyMeters); + parcel.writeFloat(mVerticalAccuracyMeters); + parcel.writeFloat(mSpeedAccuracyMetersPerSecond); + parcel.writeFloat(mBearingAccuracyDegrees); + parcel.writeBundle(mExtras); + } + + /** + * Returns one of the optional extra {@link Location}s that can be attached + * to this Location. + * + * @param key the key associated with the desired extra Location + * @return the extra Location, or null if unavailable + * @hide + */ + public Location getExtraLocation(String key) { + if (mExtras != null) { + Parcelable value = mExtras.getParcelable(key); + if (value instanceof Location) { + return (Location) value; + } + } + return null; + } + + /** + * Attaches an extra {@link Location} to this Location. + * + * @param key the key associated with the Location extra + * @param value the Location to attach + * @hide + */ + public void setExtraLocation(String key, Location value) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putParcelable(key, value); + } + + /** + * Returns true if the Location came from a mock provider. + * + * @return true if this Location came from a mock provider, false otherwise + */ + public boolean isFromMockProvider() { + return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0; + } + + /** + * Flag this Location as having come from a mock provider or not. + * + * @param isFromMockProvider true if this Location came from a mock provider, false otherwise + * @hide + */ + @SystemApi + public void setIsFromMockProvider(boolean isFromMockProvider) { + if (isFromMockProvider) { + mFieldsMask |= HAS_MOCK_PROVIDER_MASK; + } else { + mFieldsMask &= ~HAS_MOCK_PROVIDER_MASK; + } + } + + /** + * Caches data used to compute distance and bearing (so successive calls to {@link #distanceTo} + * and {@link #bearingTo} don't duplicate work. + */ + private static class BearingDistanceCache { + private double mLat1 = 0.0; + private double mLon1 = 0.0; + private double mLat2 = 0.0; + private double mLon2 = 0.0; + private float mDistance = 0.0f; + private float mInitialBearing = 0.0f; + private float mFinalBearing = 0.0f; + } +} diff --git a/android/location/LocationListener.java b/android/location/LocationListener.java new file mode 100644 index 00000000..88904c82 --- /dev/null +++ b/android/location/LocationListener.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008 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.os.Bundle; + +/** + * Used for receiving notifications from the LocationManager when + * the location has changed. These methods are called if the + * LocationListener has been registered with the location manager service + * using the {@link LocationManager#requestLocationUpdates(String, long, float, LocationListener)} + * method. + * + * <div class="special reference"> + * <h3>Developer Guides</h3> + * <p>For more information about identifying user location, read the + * <a href="{@docRoot}guide/topics/location/obtaining-user-location.html">Obtaining User + * Location</a> developer guide.</p> + * </div> + */ +public interface LocationListener { + + /** + * Called when the location has changed. + * + * <p> There are no restrictions on the use of the supplied Location object. + * + * @param location The new location, as a Location object. + */ + void onLocationChanged(Location location); + + /** + * Called when the provider status changes. This method is called when + * a provider is unable to fetch a location or if the provider has recently + * become available after a period of unavailability. + * + * @param provider the name of the location provider associated with this + * update. + * @param status {@link LocationProvider#OUT_OF_SERVICE} if the + * provider is out of service, and this is not expected to change in the + * near future; {@link LocationProvider#TEMPORARILY_UNAVAILABLE} if + * the provider is temporarily unavailable but is expected to be available + * shortly; and {@link LocationProvider#AVAILABLE} if the + * provider is currently available. + * @param extras an optional Bundle which will contain provider specific + * status variables. + * + * <p> A number of common key/value pairs for the extras Bundle are listed + * below. Providers that use any of the keys on this list must + * provide the corresponding value as described below. + * + * <ul> + * <li> satellites - the number of satellites used to derive the fix + * </ul> + */ + void onStatusChanged(String provider, int status, Bundle extras); + + /** + * Called when the provider is enabled by the user. + * + * @param provider the name of the location provider associated with this + * update. + */ + void onProviderEnabled(String provider); + + /** + * Called when the provider is disabled by the user. If requestLocationUpdates + * is called on an already disabled provider, this method is called + * immediately. + * + * @param provider the name of the location provider associated with this + * update. + */ + void onProviderDisabled(String provider); +} diff --git a/android/location/LocationManager.java b/android/location/LocationManager.java new file mode 100644 index 00000000..968f596e --- /dev/null +++ b/android/location/LocationManager.java @@ -0,0 +1,2135 @@ +/* + * Copyright (C) 2007 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 com.android.internal.location.ProviderProperties; + +import android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.TestApi; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; + +/** + * This class provides access to the system location services. These + * services allow applications to obtain periodic updates of the + * device's geographical location, or to fire an application-specified + * {@link Intent} when the device enters the proximity of a given + * geographical location. + * + * <p class="note">Unless noted, all Location API methods require + * the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permissions. + * If your application only has the coarse permission then it will not have + * access to the GPS or passive location providers. Other providers will still + * return location results, but the update rate will be throttled and the exact + * location will be obfuscated to a coarse level of accuracy. + */ +@SystemService(Context.LOCATION_SERVICE) +public class LocationManager { + private static final String TAG = "LocationManager"; + + private final Context mContext; + private final ILocationManager mService; + private final GnssMeasurementCallbackTransport mGnssMeasurementCallbackTransport; + private final GnssNavigationMessageCallbackTransport mGnssNavigationMessageCallbackTransport; + private final BatchedLocationCallbackTransport mBatchedLocationCallbackTransport; + private final HashMap<GpsStatus.Listener, GnssStatusListenerTransport> mGpsStatusListeners = + new HashMap<>(); + private final HashMap<GpsStatus.NmeaListener, GnssStatusListenerTransport> mGpsNmeaListeners = + new HashMap<>(); + private final HashMap<GnssStatus.Callback, GnssStatusListenerTransport> mGnssStatusListeners = + new HashMap<>(); + private final HashMap<OnNmeaMessageListener, GnssStatusListenerTransport> mGnssNmeaListeners = + new HashMap<>(); + // volatile + GnssStatus final-fields pattern to avoid a partially published object + private volatile GnssStatus mGnssStatus; + private int mTimeToFirstFix; + + /** + * Name of the network location provider. + * <p>This provider determines location based on + * availability of cell tower and WiFi access points. Results are retrieved + * by means of a network lookup. + */ + public static final String NETWORK_PROVIDER = "network"; + + /** + * Name of the GPS location provider. + * + * <p>This provider determines location using + * satellites. Depending on conditions, this provider may take a while to return + * a location fix. Requires the permission + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}. + * + * <p> The extras Bundle for the GPS location provider can contain the + * following key/value pairs: + * <ul> + * <li> satellites - the number of satellites used to derive the fix + * </ul> + */ + public static final String GPS_PROVIDER = "gps"; + + /** + * A special location provider for receiving locations without actually initiating + * a location fix. + * + * <p>This provider can be used to passively receive location updates + * when other applications or services request them without actually requesting + * the locations yourself. This provider will return locations generated by other + * providers. You can query the {@link Location#getProvider()} method to determine + * the origin of the location update. Requires the permission + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}, although if the GPS is + * not enabled this provider might only return coarse fixes. + */ + public static final String PASSIVE_PROVIDER = "passive"; + + /** + * Name of the Fused location provider. + * + * <p>This provider combines inputs for all possible location sources + * to provide the best possible Location fix. It is implicitly + * used for all API's that involve the {@link LocationRequest} + * object. + * + * @hide + */ + public static final String FUSED_PROVIDER = "fused"; + + /** + * Key used for the Bundle extra holding a boolean indicating whether + * a proximity alert is entering (true) or exiting (false).. + */ + public static final String KEY_PROXIMITY_ENTERING = "entering"; + + /** + * Key used for a Bundle extra holding an Integer status value + * when a status change is broadcast using a PendingIntent. + */ + public static final String KEY_STATUS_CHANGED = "status"; + + /** + * Key used for a Bundle extra holding an Boolean status value + * when a provider enabled/disabled event is broadcast using a PendingIntent. + */ + public static final String KEY_PROVIDER_ENABLED = "providerEnabled"; + + /** + * Key used for a Bundle extra holding a Location value + * when a location change is broadcast using a PendingIntent. + */ + public static final String KEY_LOCATION_CHANGED = "location"; + + /** + * Broadcast intent action indicating that the GPS has either been + * enabled or disabled. An intent extra provides this state as a boolean, + * where {@code true} means enabled. + * @see #EXTRA_GPS_ENABLED + * + * @hide + */ + public static final String GPS_ENABLED_CHANGE_ACTION = + "android.location.GPS_ENABLED_CHANGE"; + + /** + * Broadcast intent action when the configured location providers + * change. For use with {@link #isProviderEnabled(String)}. If you're interacting with the + * {@link android.provider.Settings.Secure#LOCATION_MODE} API, use {@link #MODE_CHANGED_ACTION} + * instead. + */ + public static final String PROVIDERS_CHANGED_ACTION = + "android.location.PROVIDERS_CHANGED"; + + /** + * Broadcast intent action when {@link android.provider.Settings.Secure#LOCATION_MODE} changes. + * For use with the {@link android.provider.Settings.Secure#LOCATION_MODE} API. + * If you're interacting with {@link #isProviderEnabled(String)}, use + * {@link #PROVIDERS_CHANGED_ACTION} instead. + * + * In the future, there may be mode changes that do not result in + * {@link #PROVIDERS_CHANGED_ACTION} broadcasts. + */ + public static final String MODE_CHANGED_ACTION = "android.location.MODE_CHANGED"; + + /** + * Broadcast intent action indicating that the GPS has either started or + * stopped receiving GPS fixes. An intent extra provides this state as a + * boolean, where {@code true} means that the GPS is actively receiving fixes. + * @see #EXTRA_GPS_ENABLED + * + * @hide + */ + public static final String GPS_FIX_CHANGE_ACTION = + "android.location.GPS_FIX_CHANGE"; + + /** + * The lookup key for a boolean that indicates whether GPS is enabled or + * disabled. {@code true} means GPS is enabled. Retrieve it with + * {@link android.content.Intent#getBooleanExtra(String,boolean)}. + * + * @hide + */ + public static final String EXTRA_GPS_ENABLED = "enabled"; + + /** + * Broadcast intent action indicating that a high power location requests + * has either started or stopped being active. The current state of + * active location requests should be read from AppOpsManager using + * {@code OP_MONITOR_HIGH_POWER_LOCATION}. + * + * @hide + */ + public static final String HIGH_POWER_REQUEST_CHANGE_ACTION = + "android.location.HIGH_POWER_REQUEST_CHANGE"; + + // Map from LocationListeners to their associated ListenerTransport objects + private HashMap<LocationListener,ListenerTransport> mListeners = + new HashMap<LocationListener,ListenerTransport>(); + + private class ListenerTransport extends ILocationListener.Stub { + private static final int TYPE_LOCATION_CHANGED = 1; + private static final int TYPE_STATUS_CHANGED = 2; + private static final int TYPE_PROVIDER_ENABLED = 3; + private static final int TYPE_PROVIDER_DISABLED = 4; + + private LocationListener mListener; + private final Handler mListenerHandler; + + ListenerTransport(LocationListener listener, Looper looper) { + mListener = listener; + + if (looper == null) { + mListenerHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + _handleMessage(msg); + } + }; + } else { + mListenerHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + _handleMessage(msg); + } + }; + } + } + + @Override + public void onLocationChanged(Location location) { + Message msg = Message.obtain(); + msg.what = TYPE_LOCATION_CHANGED; + msg.obj = location; + mListenerHandler.sendMessage(msg); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + Message msg = Message.obtain(); + msg.what = TYPE_STATUS_CHANGED; + Bundle b = new Bundle(); + b.putString("provider", provider); + b.putInt("status", status); + if (extras != null) { + b.putBundle("extras", extras); + } + msg.obj = b; + mListenerHandler.sendMessage(msg); + } + + @Override + public void onProviderEnabled(String provider) { + Message msg = Message.obtain(); + msg.what = TYPE_PROVIDER_ENABLED; + msg.obj = provider; + mListenerHandler.sendMessage(msg); + } + + @Override + public void onProviderDisabled(String provider) { + Message msg = Message.obtain(); + msg.what = TYPE_PROVIDER_DISABLED; + msg.obj = provider; + mListenerHandler.sendMessage(msg); + } + + private void _handleMessage(Message msg) { + switch (msg.what) { + case TYPE_LOCATION_CHANGED: + Location location = new Location((Location) msg.obj); + mListener.onLocationChanged(location); + break; + case TYPE_STATUS_CHANGED: + Bundle b = (Bundle) msg.obj; + String provider = b.getString("provider"); + int status = b.getInt("status"); + Bundle extras = b.getBundle("extras"); + mListener.onStatusChanged(provider, status, extras); + break; + case TYPE_PROVIDER_ENABLED: + mListener.onProviderEnabled((String) msg.obj); + break; + case TYPE_PROVIDER_DISABLED: + mListener.onProviderDisabled((String) msg.obj); + break; + } + try { + mService.locationCallbackFinished(this); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * @hide - hide this constructor because it has a parameter + * of type ILocationManager, which is a system private class. The + * right way to create an instance of this class is using the + * factory Context.getSystemService. + */ + public LocationManager(Context context, ILocationManager service) { + mService = service; + mContext = context; + mGnssMeasurementCallbackTransport = + new GnssMeasurementCallbackTransport(mContext, mService); + mGnssNavigationMessageCallbackTransport = + new GnssNavigationMessageCallbackTransport(mContext, mService); + mBatchedLocationCallbackTransport = + new BatchedLocationCallbackTransport(mContext, mService); + + } + + private LocationProvider createProvider(String name, ProviderProperties properties) { + return new LocationProvider(name, properties); + } + + /** + * Returns a list of the names of all known location providers. + * <p>All providers are returned, including ones that are not permitted to + * be accessed by the calling activity or are currently disabled. + * + * @return list of Strings containing names of the provider + */ + public List<String> getAllProviders() { + try { + return mService.getAllProviders(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a list of the names of location providers. + * + * @param enabledOnly if true then only the providers which are currently + * enabled are returned. + * @return list of Strings containing names of the providers + */ + public List<String> getProviders(boolean enabledOnly) { + try { + return mService.getProviders(null, enabledOnly); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the information associated with the location provider of the + * given name, or null if no provider exists by that name. + * + * @param name the provider name + * @return a LocationProvider, or null + * + * @throws IllegalArgumentException if name is null or does not exist + * @throws SecurityException if the caller is not permitted to access the + * given provider. + */ + public LocationProvider getProvider(String name) { + checkProvider(name); + try { + ProviderProperties properties = mService.getProviderProperties(name); + if (properties == null) { + return null; + } + return createProvider(name, properties); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a list of the names of LocationProviders that satisfy the given + * criteria, or null if none do. Only providers that are permitted to be + * accessed by the calling activity will be returned. + * + * @param criteria the criteria that the returned providers must match + * @param enabledOnly if true then only the providers which are currently + * enabled are returned. + * @return list of Strings containing names of the providers + */ + public List<String> getProviders(Criteria criteria, boolean enabledOnly) { + checkCriteria(criteria); + try { + return mService.getProviders(criteria, enabledOnly); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the name of the provider that best meets the given criteria. Only providers + * that are permitted to be accessed by the calling activity will be + * returned. If several providers meet the criteria, the one with the best + * accuracy is returned. If no provider meets the criteria, + * the criteria are loosened in the following sequence: + * + * <ul> + * <li> power requirement + * <li> accuracy + * <li> bearing + * <li> speed + * <li> altitude + * </ul> + * + * <p> Note that the requirement on monetary cost is not removed + * in this process. + * + * @param criteria the criteria that need to be matched + * @param enabledOnly if true then only a provider that is currently enabled is returned + * @return name of the provider that best matches the requirements + */ + public String getBestProvider(Criteria criteria, boolean enabledOnly) { + checkCriteria(criteria); + try { + return mService.getBestProvider(criteria, enabledOnly); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Register for location updates using the named provider, and a + * pending intent. + * + * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} + * for more detail on how to use this method. + * + * @param provider the name of the provider with which to register + * @param minTime minimum time interval between location updates, in milliseconds + * @param minDistance minimum distance between location updates, in meters + * @param listener a {@link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called for + * each location update + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * on this device + * @throws IllegalArgumentException if listener is null + * @throws RuntimeException if the calling thread has no Looper + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestLocationUpdates(String provider, long minTime, float minDistance, + LocationListener listener) { + checkProvider(provider); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, minTime, minDistance, false); + requestLocationUpdates(request, listener, null, null); + } + + /** + * Register for location updates using the named provider, and a callback on + * the specified looper thread. + * + * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} + * for more detail on how to use this method. + * + * @param provider the name of the provider with which to register + * @param minTime minimum time interval between location updates, in milliseconds + * @param minDistance minimum distance between location updates, in meters + * @param listener a {@link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called for + * each location update + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism, or null to make callbacks on the calling + * thread + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if listener is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestLocationUpdates(String provider, long minTime, float minDistance, + LocationListener listener, Looper looper) { + checkProvider(provider); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, minTime, minDistance, false); + requestLocationUpdates(request, listener, looper, null); + } + + /** + * Register for location updates using a Criteria, and a callback + * on the specified looper thread. + * + * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} + * for more detail on how to use this method. + * + * @param minTime minimum time interval between location updates, in milliseconds + * @param minDistance minimum distance between location updates, in meters + * @param criteria contains parameters for the location manager to choose the + * appropriate provider and parameters to compute the location + * @param listener a {@link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called for + * each location update + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism, or null to make callbacks on the calling + * thread + * + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if listener is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestLocationUpdates(long minTime, float minDistance, Criteria criteria, + LocationListener listener, Looper looper) { + checkCriteria(criteria); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, minTime, minDistance, false); + requestLocationUpdates(request, listener, looper, null); + } + + /** + * Register for location updates using the named provider, and a + * pending intent. + * + * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} + * for more detail on how to use this method. + * + * @param provider the name of the provider with which to register + * @param minTime minimum time interval between location updates, in milliseconds + * @param minDistance minimum distance between location updates, in meters + * @param intent a {@link PendingIntent} to be sent for each location update + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * on this device + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestLocationUpdates(String provider, long minTime, float minDistance, + PendingIntent intent) { + checkProvider(provider); + checkPendingIntent(intent); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, minTime, minDistance, false); + requestLocationUpdates(request, null, null, intent); + } + + /** + * Register for location updates using a Criteria and pending intent. + * + * <p>The <code>requestLocationUpdates()</code> and + * <code>requestSingleUpdate()</code> register the current activity to be + * updated periodically by the named provider, or by the provider matching + * the specified {@link Criteria}, with location and status updates. + * + * <p> It may take a while to receive the first location update. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * <p> Location updates are received either by {@link LocationListener} + * callbacks, or by broadcast intents to a supplied {@link PendingIntent}. + * + * <p> If the caller supplied a pending intent, then location updates + * are sent with a key of {@link #KEY_LOCATION_CHANGED} and a + * {@link android.location.Location} value. + * + * <p> The location update interval can be controlled using the minTime parameter. + * The elapsed time between location updates will never be less than + * minTime, although it can be more depending on the Location Provider + * implementation and the update interval requested by other applications. + * + * <p> Choosing a sensible value for minTime is important to conserve + * battery life. Each location update requires power from + * GPS, WIFI, Cell and other radios. Select a minTime value as high as + * possible while still providing a reasonable user experience. + * If your application is not in the foreground and showing + * location to the user then your application should avoid using an active + * provider (such as {@link #NETWORK_PROVIDER} or {@link #GPS_PROVIDER}), + * but if you insist then select a minTime of 5 * 60 * 1000 (5 minutes) + * or greater. If your application is in the foreground and showing + * location to the user then it is appropriate to select a faster + * update interval. + * + * <p> The minDistance parameter can also be used to control the + * frequency of location updates. If it is greater than 0 then the + * location provider will only send your application an update when + * the location has changed by at least minDistance meters, AND + * at least minTime milliseconds have passed. However it is more + * difficult for location providers to save power using the minDistance + * parameter, so minTime should be the primary tool to conserving battery + * life. + * + * <p> If your application wants to passively observe location + * updates triggered by other applications, but not consume + * any additional power otherwise, then use the {@link #PASSIVE_PROVIDER} + * This provider does not actively turn on or modify active location + * providers, so you do not need to be as careful about minTime and + * minDistance. However if your application performs heavy work + * on a location update (such as network activity) then you should + * select non-zero values for minTime and/or minDistance to rate-limit + * your update frequency in the case another application enables a + * location provider with extremely fast updates. + * + * <p>In case the provider is disabled by the user, updates will stop, + * and a provider availability update will be sent. + * As soon as the provider is enabled again, + * location updates will immediately resume and a provider availability + * update sent. Providers can also send status updates, at any time, + * with extra's specific to the provider. If a callback was supplied + * then status and availability updates are via + * {@link LocationListener#onProviderDisabled}, + * {@link LocationListener#onProviderEnabled} or + * {@link LocationListener#onStatusChanged}. Alternately, if a + * pending intent was supplied then status and availability updates + * are broadcast intents with extra keys of + * {@link #KEY_PROVIDER_ENABLED} or {@link #KEY_STATUS_CHANGED}. + * + * <p> If a {@link LocationListener} is used but with no Looper specified + * then the calling thread must already + * be a {@link android.os.Looper} thread such as the main thread of the + * calling Activity. If a Looper is specified with a {@link LocationListener} + * then callbacks are made on the supplied Looper thread. + * + * <p class="note"> Prior to Jellybean, the minTime parameter was + * only a hint, and some location provider implementations ignored it. + * From Jellybean and onwards it is mandatory for Android compatible + * devices to observe both the minTime and minDistance parameters. + * + * @param minTime minimum time interval between location updates, in milliseconds + * @param minDistance minimum distance between location updates, in meters + * @param criteria contains parameters for the location manager to choose the + * appropriate provider and parameters to compute the location + * @param intent a {@link PendingIntent} to be sent for each location update + * + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestLocationUpdates(long minTime, float minDistance, Criteria criteria, + PendingIntent intent) { + checkCriteria(criteria); + checkPendingIntent(intent); + + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, minTime, minDistance, false); + requestLocationUpdates(request, null, null, intent); + } + + /** + * Register for a single location update using the named provider and + * a callback. + * + * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} + * for more detail on how to use this method. + * + * @param provider the name of the provider with which to register + * @param listener a {@link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called when + * the location update is available + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism, or null to make callbacks on the calling + * thread + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if listener is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestSingleUpdate(String provider, LocationListener listener, Looper looper) { + checkProvider(provider); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, 0, 0, true); + requestLocationUpdates(request, listener, looper, null); + } + + /** + * Register for a single location update using a Criteria and + * a callback. + * + * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} + * for more detail on how to use this method. + * + * @param criteria contains parameters for the location manager to choose the + * appropriate provider and parameters to compute the location + * @param listener a {@link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called when + * the location update is available + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism, or null to make callbacks on the calling + * thread + * + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if listener is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestSingleUpdate(Criteria criteria, LocationListener listener, Looper looper) { + checkCriteria(criteria); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, 0, 0, true); + requestLocationUpdates(request, listener, looper, null); + } + + /** + * Register for a single location update using a named provider and pending intent. + * + * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} + * for more detail on how to use this method. + * + * @param provider the name of the provider with which to register + * @param intent a {@link PendingIntent} to be sent for the location update + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestSingleUpdate(String provider, PendingIntent intent) { + checkProvider(provider); + checkPendingIntent(intent); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, 0, 0, true); + requestLocationUpdates(request, null, null, intent); + } + + /** + * Register for a single location update using a Criteria and pending intent. + * + * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)} + * for more detail on how to use this method. + * + * @param criteria contains parameters for the location manager to choose the + * appropriate provider and parameters to compute the location + * @param intent a {@link PendingIntent} to be sent for the location update + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if no suitable permission is present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestSingleUpdate(Criteria criteria, PendingIntent intent) { + checkCriteria(criteria); + checkPendingIntent(intent); + + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, 0, 0, true); + requestLocationUpdates(request, null, null, intent); + } + + /** + * Register for fused location updates using a LocationRequest and callback. + * + * <p>Upon a location update, the system delivers the new {@link Location} to the + * provided {@link LocationListener}, by calling its {@link + * LocationListener#onLocationChanged} method.</p> + * + * <p>The system will automatically select and enable the best providers + * to compute a location for your application. It may use only passive + * locations, or just a single location source, or it may fuse together + * multiple location sources in order to produce the best possible + * result, depending on the quality of service requested in the + * {@link LocationRequest}. + * + * <p>LocationRequest can be null, in which case the system will choose + * default, low power parameters for location updates. You will occasionally + * receive location updates as available, without a major power impact on the + * system. If your application just needs an occasional location update + * without any strict demands, then pass a null LocationRequest. + * + * <p>Only one LocationRequest can be registered for each unique callback + * or pending intent. So a subsequent request with the same callback or + * pending intent will over-write the previous LocationRequest. + * + * <p> If a pending intent is supplied then location updates + * are sent with a key of {@link #KEY_LOCATION_CHANGED} and a + * {@link android.location.Location} value. If a callback is supplied + * then location updates are made using the + * {@link LocationListener#onLocationChanged} callback, on the specified + * Looper thread. If a {@link LocationListener} is used + * but with a null Looper then the calling thread must already + * be a {@link android.os.Looper} thread (such as the main thread) and + * callbacks will occur on this thread. + * + * <p> Provider status updates and availability updates are deprecated + * because the system is performing provider fusion on the applications + * behalf. So {@link LocationListener#onProviderDisabled}, + * {@link LocationListener#onProviderEnabled}, {@link LocationListener#onStatusChanged} + * will not be called, and intents with extra keys of + * {@link #KEY_PROVIDER_ENABLED} or {@link #KEY_STATUS_CHANGED} will not + * be received. + * + * <p> To unregister for Location updates, use: {@link #removeUpdates(LocationListener)}. + * + * @param request quality of service required, null for default low power + * @param listener a {@link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called when + * the location update is available + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism, or null to make callbacks on the calling + * thread + * + * @throws IllegalArgumentException if listener is null + * @throws SecurityException if no suitable permission is present + * + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestLocationUpdates(LocationRequest request, LocationListener listener, + Looper looper) { + checkListener(listener); + requestLocationUpdates(request, listener, looper, null); + } + + + /** + * Register for fused location updates using a LocationRequest and a pending intent. + * + * <p>Upon a location update, the system delivers the new {@link Location} with your provided + * {@link PendingIntent}, as the value for {@link LocationManager#KEY_LOCATION_CHANGED} + * in the intent's extras.</p> + * + * <p> To unregister for Location updates, use: {@link #removeUpdates(PendingIntent)}. + * + * <p> See {@link #requestLocationUpdates(LocationRequest, LocationListener, Looper)} + * for more detail. + * + * @param request quality of service required, null for default low power + * @param intent a {@link PendingIntent} to be sent for the location update + * + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if no suitable permission is present + * + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void requestLocationUpdates(LocationRequest request, PendingIntent intent) { + checkPendingIntent(intent); + requestLocationUpdates(request, null, null, intent); + } + + private ListenerTransport wrapListener(LocationListener listener, Looper looper) { + if (listener == null) return null; + synchronized (mListeners) { + ListenerTransport transport = mListeners.get(listener); + if (transport == null) { + transport = new ListenerTransport(listener, looper); + } + mListeners.put(listener, transport); + return transport; + } + } + + private void requestLocationUpdates(LocationRequest request, LocationListener listener, + Looper looper, PendingIntent intent) { + + String packageName = mContext.getPackageName(); + + // wrap the listener class + ListenerTransport transport = wrapListener(listener, looper); + + try { + mService.requestLocationUpdates(request, transport, intent, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes all location updates for the specified LocationListener. + * + * <p>Following this call, updates will no longer + * occur for this listener. + * + * @param listener listener object that no longer needs location updates + * @throws IllegalArgumentException if listener is null + */ + public void removeUpdates(LocationListener listener) { + checkListener(listener); + String packageName = mContext.getPackageName(); + + ListenerTransport transport; + synchronized (mListeners) { + transport = mListeners.remove(listener); + } + if (transport == null) return; + + try { + mService.removeUpdates(transport, null, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes all location updates for the specified pending intent. + * + * <p>Following this call, updates will no longer for this pending intent. + * + * @param intent pending intent object that no longer needs location updates + * @throws IllegalArgumentException if intent is null + */ + public void removeUpdates(PendingIntent intent) { + checkPendingIntent(intent); + String packageName = mContext.getPackageName(); + + try { + mService.removeUpdates(null, intent, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set a proximity alert for the location given by the position + * (latitude, longitude) and the given radius. + * + * <p> When the device + * detects that it has entered or exited the area surrounding the + * location, the given PendingIntent will be used to create an Intent + * to be fired. + * + * <p> The fired Intent will have a boolean extra added with key + * {@link #KEY_PROXIMITY_ENTERING}. If the value is true, the device is + * entering the proximity region; if false, it is exiting. + * + * <p> Due to the approximate nature of position estimation, if the + * device passes through the given area briefly, it is possible + * that no Intent will be fired. Similarly, an Intent could be + * fired if the device passes very close to the given area but + * does not actually enter it. + * + * <p> After the number of milliseconds given by the expiration + * parameter, the location manager will delete this proximity + * alert and no longer monitor it. A value of -1 indicates that + * there should be no expiration time. + * + * <p> Internally, this method uses both {@link #NETWORK_PROVIDER} + * and {@link #GPS_PROVIDER}. + * + * <p>Before API version 17, this method could be used with + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} or + * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}. + * From API version 17 and onwards, this method requires + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission. + * + * @param latitude the latitude of the central point of the + * alert region + * @param longitude the longitude of the central point of the + * alert region + * @param radius the radius of the central point of the + * alert region, in meters + * @param expiration time for this proximity alert, in milliseconds, + * or -1 to indicate no expiration + * @param intent a PendingIntent that will be used to generate an Intent to + * fire when entry to or exit from the alert region is detected + * + * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION} + * permission is not present + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void addProximityAlert(double latitude, double longitude, float radius, long expiration, + PendingIntent intent) { + checkPendingIntent(intent); + if (expiration < 0) expiration = Long.MAX_VALUE; + + Geofence fence = Geofence.createCircle(latitude, longitude, radius); + LocationRequest request = new LocationRequest().setExpireIn(expiration); + try { + mService.requestGeofence(request, fence, intent, mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Add a geofence with the specified LocationRequest quality of service. + * + * <p> When the device + * detects that it has entered or exited the area surrounding the + * location, the given PendingIntent will be used to create an Intent + * to be fired. + * + * <p> The fired Intent will have a boolean extra added with key + * {@link #KEY_PROXIMITY_ENTERING}. If the value is true, the device is + * entering the proximity region; if false, it is exiting. + * + * <p> The geofence engine fuses results from all location providers to + * provide the best balance between accuracy and power. Applications + * can choose the quality of service required using the + * {@link LocationRequest} object. If it is null then a default, + * low power geo-fencing implementation is used. It is possible to cross + * a geo-fence without notification, but the system will do its best + * to detect, using {@link LocationRequest} as a hint to trade-off + * accuracy and power. + * + * <p> The power required by the geofence engine can depend on many factors, + * such as quality and interval requested in {@link LocationRequest}, + * distance to nearest geofence and current device velocity. + * + * @param request quality of service required, null for default low power + * @param fence a geographical description of the geofence area + * @param intent pending intent to receive geofence updates + * + * @throws IllegalArgumentException if fence is null + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION} + * permission is not present + * + * @hide + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public void addGeofence(LocationRequest request, Geofence fence, PendingIntent intent) { + checkPendingIntent(intent); + checkGeofence(fence); + + try { + mService.requestGeofence(request, fence, intent, mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes the proximity alert with the given PendingIntent. + * + * <p>Before API version 17, this method could be used with + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} or + * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}. + * From API version 17 and onwards, this method requires + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission. + * + * @param intent the PendingIntent that no longer needs to be notified of + * proximity alerts + * + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION} + * permission is not present + */ + public void removeProximityAlert(PendingIntent intent) { + checkPendingIntent(intent); + String packageName = mContext.getPackageName(); + + try { + mService.removeGeofence(null, intent, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove a single geofence. + * + * <p>This removes only the specified geofence associated with the + * specified pending intent. All other geofences remain unchanged. + * + * @param fence a geofence previously passed to {@link #addGeofence} + * @param intent a pending intent previously passed to {@link #addGeofence} + * + * @throws IllegalArgumentException if fence is null + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION} + * permission is not present + * + * @hide + */ + public void removeGeofence(Geofence fence, PendingIntent intent) { + checkPendingIntent(intent); + checkGeofence(fence); + String packageName = mContext.getPackageName(); + + try { + mService.removeGeofence(fence, intent, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove all geofences registered to the specified pending intent. + * + * @param intent a pending intent previously passed to {@link #addGeofence} + * + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION} + * permission is not present + * + * @hide + */ + public void removeAllGeofences(PendingIntent intent) { + checkPendingIntent(intent); + String packageName = mContext.getPackageName(); + + try { + mService.removeGeofence(null, intent, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the current enabled/disabled status of the given provider. + * + * <p>If the user has enabled this provider in the Settings menu, true + * is returned otherwise false is returned + * + * <p>Callers should instead use + * {@link android.provider.Settings.Secure#LOCATION_MODE} + * unless they depend on provider-specific APIs such as + * {@link #requestLocationUpdates(String, long, float, LocationListener)}. + * + * <p> + * Before API version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this + * method would throw {@link SecurityException} if the location permissions + * were not sufficient to use the specified provider. + * + * @param provider the name of the provider + * @return true if the provider exists and is enabled + * + * @throws IllegalArgumentException if provider is null + */ + public boolean isProviderEnabled(String provider) { + checkProvider(provider); + + try { + return mService.isProviderEnabled(provider); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the last known location. + * + * <p>This location could be very old so use + * {@link Location#getElapsedRealtimeNanos} to calculate its age. It can + * also return null if no previous location is available. + * + * <p>Always returns immediately. + * + * @return The last known location, or null if not available + * @throws SecurityException if no suitable permission is present + * + * @hide + */ + public Location getLastLocation() { + String packageName = mContext.getPackageName(); + + try { + return mService.getLastLocation(null, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns a Location indicating the data from the last known + * location fix obtained from the given provider. + * + * <p> This can be done + * without starting the provider. Note that this location could + * be out-of-date, for example if the device was turned off and + * moved to another location. + * + * <p> If the provider is currently disabled, null is returned. + * + * @param provider the name of the provider + * @return the last known location for the provider, or null + * + * @throws SecurityException if no suitable permission is present + * @throws IllegalArgumentException if provider is null or doesn't exist + */ + @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}) + public Location getLastKnownLocation(String provider) { + checkProvider(provider); + String packageName = mContext.getPackageName(); + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, 0, 0, true); + + try { + return mService.getLastLocation(request, packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + // --- Mock provider support --- + // TODO: It would be fantastic to deprecate mock providers entirely, and replace + // with something closer to LocationProviderBase.java + + /** + * Creates a mock location provider and adds it to the set of active providers. + * + * @param name the provider name + * + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + * @throws IllegalArgumentException if a provider with the given name already exists + */ + public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + ProviderProperties properties = new ProviderProperties(requiresNetwork, + requiresSatellite, requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, + supportsBearing, powerRequirement, accuracy); + if (name.matches(LocationProvider.BAD_CHARS_REGEX)) { + throw new IllegalArgumentException("provider name contains illegal character: " + name); + } + + try { + mService.addTestProvider(name, properties, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes the mock location provider with the given name. + * + * @param provider the provider name + * + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + * @throws IllegalArgumentException if no provider with the given name exists + */ + public void removeTestProvider(String provider) { + try { + mService.removeTestProvider(provider, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets a mock location for the given provider. + * <p>This location will be used in place of any actual location from the provider. + * The location object must have a minimum number of fields set to be + * considered a valid LocationProvider Location, as per documentation + * on {@link Location} class. + * + * @param provider the provider name + * @param loc the mock location + * + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + * @throws IllegalArgumentException if no provider with the given name exists + * @throws IllegalArgumentException if the location is incomplete + */ + public void setTestProviderLocation(String provider, Location loc) { + if (!loc.isComplete()) { + IllegalArgumentException e = new IllegalArgumentException( + "Incomplete location object, missing timestamp or accuracy? " + loc); + if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) { + // just log on old platform (for backwards compatibility) + Log.w(TAG, e); + loc.makeComplete(); + } else { + // really throw it! + throw e; + } + } + + try { + mService.setTestProviderLocation(provider, loc, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes any mock location associated with the given provider. + * + * @param provider the provider name + * + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + * @throws IllegalArgumentException if no provider with the given name exists + */ + public void clearTestProviderLocation(String provider) { + try { + mService.clearTestProviderLocation(provider, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets a mock enabled value for the given provider. This value will be used in place + * of any actual value from the provider. + * + * @param provider the provider name + * @param enabled the mock enabled value + * + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + * @throws IllegalArgumentException if no provider with the given name exists + */ + public void setTestProviderEnabled(String provider, boolean enabled) { + try { + mService.setTestProviderEnabled(provider, enabled, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes any mock enabled value associated with the given provider. + * + * @param provider the provider name + * + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + * @throws IllegalArgumentException if no provider with the given name exists + */ + public void clearTestProviderEnabled(String provider) { + try { + mService.clearTestProviderEnabled(provider, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets mock status values for the given provider. These values will be used in place + * of any actual values from the provider. + * + * @param provider the provider name + * @param status the mock status + * @param extras a Bundle containing mock extras + * @param updateTime the mock update time + * + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + * @throws IllegalArgumentException if no provider with the given name exists + */ + public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) { + try { + mService.setTestProviderStatus(provider, status, extras, updateTime, + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes any mock status values associated with the given provider. + * + * @param provider the provider name + * + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + * @throws IllegalArgumentException if no provider with the given name exists + */ + public void clearTestProviderStatus(String provider) { + try { + mService.clearTestProviderStatus(provider, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + // --- GPS-specific support --- + + // This class is used to send Gnss status events to the client's specific thread. + private class GnssStatusListenerTransport extends IGnssStatusListener.Stub { + + private final GpsStatus.Listener mGpsListener; + private final GpsStatus.NmeaListener mGpsNmeaListener; + private final GnssStatus.Callback mGnssCallback; + private final OnNmeaMessageListener mGnssNmeaListener; + + private class GnssHandler extends Handler { + public GnssHandler(Handler handler) { + super(handler != null ? handler.getLooper() : Looper.myLooper()); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case NMEA_RECEIVED: + synchronized (mNmeaBuffer) { + int length = mNmeaBuffer.size(); + for (int i = 0; i < length; i++) { + Nmea nmea = mNmeaBuffer.get(i); + mGnssNmeaListener.onNmeaMessage(nmea.mNmea, nmea.mTimestamp); + } + mNmeaBuffer.clear(); + } + break; + case GpsStatus.GPS_EVENT_STARTED: + mGnssCallback.onStarted(); + break; + case GpsStatus.GPS_EVENT_STOPPED: + mGnssCallback.onStopped(); + break; + case GpsStatus.GPS_EVENT_FIRST_FIX: + mGnssCallback.onFirstFix(mTimeToFirstFix); + break; + case GpsStatus.GPS_EVENT_SATELLITE_STATUS: + mGnssCallback.onSatelliteStatusChanged(mGnssStatus); + break; + default: + break; + } + } + } + + private final Handler mGnssHandler; + + // This must not equal any of the GpsStatus event IDs + private static final int NMEA_RECEIVED = 1000; + + private class Nmea { + long mTimestamp; + String mNmea; + + Nmea(long timestamp, String nmea) { + mTimestamp = timestamp; + mNmea = nmea; + } + } + private final ArrayList<Nmea> mNmeaBuffer; + + GnssStatusListenerTransport(GpsStatus.Listener listener) { + this(listener, null); + } + + GnssStatusListenerTransport(GpsStatus.Listener listener, Handler handler) { + mGpsListener = listener; + mGnssHandler = new GnssHandler(handler); + mGpsNmeaListener = null; + mNmeaBuffer = null; + mGnssCallback = mGpsListener != null ? new GnssStatus.Callback() { + @Override + public void onStarted() { + mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED); + } + + @Override + public void onStopped() { + mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STOPPED); + } + + @Override + public void onFirstFix(int ttff) { + mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_FIRST_FIX); + } + + @Override + public void onSatelliteStatusChanged(GnssStatus status) { + mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS); + } + } : null; + mGnssNmeaListener = null; + } + + GnssStatusListenerTransport(GpsStatus.NmeaListener listener) { + this(listener, null); + } + + GnssStatusListenerTransport(GpsStatus.NmeaListener listener, Handler handler) { + mGpsListener = null; + mGnssHandler = new GnssHandler(handler); + mGpsNmeaListener = listener; + mNmeaBuffer = new ArrayList<Nmea>(); + mGnssCallback = null; + mGnssNmeaListener = mGpsNmeaListener != null ? new OnNmeaMessageListener() { + @Override + public void onNmeaMessage(String nmea, long timestamp) { + mGpsNmeaListener.onNmeaReceived(timestamp, nmea); + } + } : null; + } + + GnssStatusListenerTransport(GnssStatus.Callback callback) { + this(callback, null); + } + + GnssStatusListenerTransport(GnssStatus.Callback callback, Handler handler) { + mGnssCallback = callback; + mGnssHandler = new GnssHandler(handler); + mGnssNmeaListener = null; + mNmeaBuffer = null; + mGpsListener = null; + mGpsNmeaListener = null; + } + + GnssStatusListenerTransport(OnNmeaMessageListener listener) { + this(listener, null); + } + + GnssStatusListenerTransport(OnNmeaMessageListener listener, Handler handler) { + mGnssCallback = null; + mGnssHandler = new GnssHandler(handler); + mGnssNmeaListener = listener; + mGpsListener = null; + mGpsNmeaListener = null; + mNmeaBuffer = new ArrayList<Nmea>(); + } + + @Override + public void onGnssStarted() { + if (mGnssCallback != null) { + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_STARTED; + mGnssHandler.sendMessage(msg); + } + } + + @Override + public void onGnssStopped() { + if (mGnssCallback != null) { + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_STOPPED; + mGnssHandler.sendMessage(msg); + } + } + + @Override + public void onFirstFix(int ttff) { + if (mGnssCallback != null) { + mTimeToFirstFix = ttff; + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_FIRST_FIX; + mGnssHandler.sendMessage(msg); + } + } + + @Override + public void onSvStatusChanged(int svCount, int[] prnWithFlags, + float[] cn0s, float[] elevations, float[] azimuths, float[] carrierFreqs) { + if (mGnssCallback != null) { + mGnssStatus = new GnssStatus(svCount, prnWithFlags, cn0s, elevations, azimuths, + carrierFreqs); + + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS; + // remove any SV status messages already in the queue + mGnssHandler.removeMessages(GpsStatus.GPS_EVENT_SATELLITE_STATUS); + mGnssHandler.sendMessage(msg); + } + } + + @Override + public void onNmeaReceived(long timestamp, String nmea) { + if (mGnssNmeaListener != null) { + synchronized (mNmeaBuffer) { + mNmeaBuffer.add(new Nmea(timestamp, nmea)); + } + Message msg = Message.obtain(); + msg.what = NMEA_RECEIVED; + // remove any NMEA_RECEIVED messages already in the queue + mGnssHandler.removeMessages(NMEA_RECEIVED); + mGnssHandler.sendMessage(msg); + } + } + } + + /** + * Adds a GPS status listener. + * + * @param listener GPS status listener object to register + * + * @return true if the listener was successfully added + * + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead. + */ + @Deprecated + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean addGpsStatusListener(GpsStatus.Listener listener) { + boolean result; + + if (mGpsStatusListeners.get(listener) != null) { + // listener is already registered + return true; + } + try { + GnssStatusListenerTransport transport = new GnssStatusListenerTransport(listener); + result = mService.registerGnssStatusCallback(transport, mContext.getPackageName()); + if (result) { + mGpsStatusListeners.put(listener, transport); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return result; + } + + /** + * Removes a GPS status listener. + * + * @param listener GPS status listener object to remove + * @deprecated use {@link #unregisterGnssStatusCallback(GnssStatus.Callback)} instead. + */ + @Deprecated + public void removeGpsStatusListener(GpsStatus.Listener listener) { + try { + GnssStatusListenerTransport transport = mGpsStatusListeners.remove(listener); + if (transport != null) { + mService.unregisterGnssStatusCallback(transport); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers a GNSS status callback. + * + * @param callback GNSS status callback object to register + * + * @return true if the listener was successfully added + * + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + */ + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean registerGnssStatusCallback(GnssStatus.Callback callback) { + return registerGnssStatusCallback(callback, null); + } + + /** + * Registers a GNSS status callback. + * + * @param callback GNSS status callback object to register + * @param handler the handler that the callback runs on. + * + * @return true if the listener was successfully added + * + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + */ + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean registerGnssStatusCallback(GnssStatus.Callback callback, Handler handler) { + boolean result; + if (mGnssStatusListeners.get(callback) != null) { + // listener is already registered + return true; + } + try { + GnssStatusListenerTransport transport = + new GnssStatusListenerTransport(callback, handler); + result = mService.registerGnssStatusCallback(transport, mContext.getPackageName()); + if (result) { + mGnssStatusListeners.put(callback, transport); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return result; + } + + /** + * Removes a GNSS status callback. + * + * @param callback GNSS status callback object to remove + */ + public void unregisterGnssStatusCallback(GnssStatus.Callback callback) { + try { + GnssStatusListenerTransport transport = mGnssStatusListeners.remove(callback); + if (transport != null) { + mService.unregisterGnssStatusCallback(transport); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds an NMEA listener. + * + * @param listener a {@link GpsStatus.NmeaListener} object to register + * + * @return true if the listener was successfully added + * + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + * @deprecated use {@link #addNmeaListener(OnNmeaMessageListener)} instead. + */ + @Deprecated + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean addNmeaListener(GpsStatus.NmeaListener listener) { + boolean result; + + if (mGpsNmeaListeners.get(listener) != null) { + // listener is already registered + return true; + } + try { + GnssStatusListenerTransport transport = new GnssStatusListenerTransport(listener); + result = mService.registerGnssStatusCallback(transport, mContext.getPackageName()); + if (result) { + mGpsNmeaListeners.put(listener, transport); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return result; + } + + /** + * Removes an NMEA listener. + * + * @param listener a {@link GpsStatus.NmeaListener} object to remove + * @deprecated use {@link #removeNmeaListener(OnNmeaMessageListener)} instead. + */ + @Deprecated + public void removeNmeaListener(GpsStatus.NmeaListener listener) { + try { + GnssStatusListenerTransport transport = mGpsNmeaListeners.remove(listener); + if (transport != null) { + mService.unregisterGnssStatusCallback(transport); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds an NMEA listener. + * + * @param listener a {@link OnNmeaMessageListener} object to register + * + * @return true if the listener was successfully added + * + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + */ + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean addNmeaListener(OnNmeaMessageListener listener) { + return addNmeaListener(listener, null); + } + + /** + * Adds an NMEA listener. + * + * @param listener a {@link OnNmeaMessageListener} object to register + * @param handler the handler that the listener runs on. + * + * @return true if the listener was successfully added + * + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + */ + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean addNmeaListener(OnNmeaMessageListener listener, Handler handler) { + boolean result; + + if (mGpsNmeaListeners.get(listener) != null) { + // listener is already registered + return true; + } + try { + GnssStatusListenerTransport transport = + new GnssStatusListenerTransport(listener, handler); + result = mService.registerGnssStatusCallback(transport, mContext.getPackageName()); + if (result) { + mGnssNmeaListeners.put(listener, transport); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return result; + } + + /** + * Removes an NMEA listener. + * + * @param listener a {@link OnNmeaMessageListener} object to remove + */ + public void removeNmeaListener(OnNmeaMessageListener listener) { + try { + GnssStatusListenerTransport transport = mGnssNmeaListeners.remove(listener); + if (transport != null) { + mService.unregisterGnssStatusCallback(transport); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * No-op method to keep backward-compatibility. + * Don't use it. Use {@link #registerGnssMeasurementsCallback} instead. + * @hide + * @deprecated Not supported anymore. + */ + @Deprecated + @SystemApi + @SuppressLint("Doclava125") + public boolean addGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) { + return false; + } + + /** + * Registers a GPS Measurement callback. + * + * @param callback a {@link GnssMeasurementsEvent.Callback} object to register. + * @return {@code true} if the callback was added successfully, {@code false} otherwise. + */ + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean registerGnssMeasurementsCallback(GnssMeasurementsEvent.Callback callback) { + return registerGnssMeasurementsCallback(callback, null); + } + + /** + * Registers a GPS Measurement callback. + * + * @param callback a {@link GnssMeasurementsEvent.Callback} object to register. + * @param handler the handler that the callback runs on. + * @return {@code true} if the callback was added successfully, {@code false} otherwise. + */ + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean registerGnssMeasurementsCallback(GnssMeasurementsEvent.Callback callback, + Handler handler) { + return mGnssMeasurementCallbackTransport.add(callback, handler); + } + + /** + * No-op method to keep backward-compatibility. + * Don't use it. Use {@link #unregisterGnssMeasurementsCallback} instead. + * @hide + * @deprecated use {@link #unregisterGnssMeasurementsCallback(GnssMeasurementsEvent.Callback)} + * instead. + */ + @Deprecated + @SystemApi + @SuppressLint("Doclava125") + public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) { + } + + /** + * Unregisters a GPS Measurement callback. + * + * @param callback a {@link GnssMeasurementsEvent.Callback} object to remove. + */ + public void unregisterGnssMeasurementsCallback(GnssMeasurementsEvent.Callback callback) { + mGnssMeasurementCallbackTransport.remove(callback); + } + + /** + * No-op method to keep backward-compatibility. + * Don't use it. Use {@link #registerGnssNavigationMessageCallback} instead. + * @hide + * @deprecated Not supported anymore. + */ + @Deprecated + @SystemApi + @SuppressLint("Doclava125") + public boolean addGpsNavigationMessageListener(GpsNavigationMessageEvent.Listener listener) { + return false; + } + + /** + * No-op method to keep backward-compatibility. + * Don't use it. Use {@link #unregisterGnssNavigationMessageCallback} instead. + * @hide + * @deprecated use + * {@link #unregisterGnssNavigationMessageCallback(GnssNavigationMessage.Callback)} + * instead + */ + @Deprecated + @SystemApi + @SuppressLint("Doclava125") + public void removeGpsNavigationMessageListener(GpsNavigationMessageEvent.Listener listener) { + } + + /** + * Registers a GNSS Navigation Message callback. + * + * @param callback a {@link GnssNavigationMessage.Callback} object to register. + * @return {@code true} if the callback was added successfully, {@code false} otherwise. + */ + public boolean registerGnssNavigationMessageCallback( + GnssNavigationMessage.Callback callback) { + return registerGnssNavigationMessageCallback(callback, null); + } + + /** + * Registers a GNSS Navigation Message callback. + * + * @param callback a {@link GnssNavigationMessage.Callback} object to register. + * @param handler the handler that the callback runs on. + * @return {@code true} if the callback was added successfully, {@code false} otherwise. + */ + @RequiresPermission(ACCESS_FINE_LOCATION) + public boolean registerGnssNavigationMessageCallback( + GnssNavigationMessage.Callback callback, Handler handler) { + return mGnssNavigationMessageCallbackTransport.add(callback, handler); + } + + /** + * Unregisters a GNSS Navigation Message callback. + * + * @param callback a {@link GnssNavigationMessage.Callback} object to remove. + */ + public void unregisterGnssNavigationMessageCallback( + GnssNavigationMessage.Callback callback) { + mGnssNavigationMessageCallbackTransport.remove(callback); + } + + /** + * Retrieves information about the current status of the GPS engine. + * This should only be called from the {@link GpsStatus.Listener#onGpsStatusChanged} + * callback to ensure that the data is copied atomically. + * + * The caller may either pass in a {@link GpsStatus} object to set with the latest + * status information, or pass null to create a new {@link GpsStatus} object. + * + * @param status object containing GPS status details, or null. + * @return status object containing updated GPS status. + */ + @Deprecated + @RequiresPermission(ACCESS_FINE_LOCATION) + public GpsStatus getGpsStatus(GpsStatus status) { + if (status == null) { + status = new GpsStatus(); + } + // When mGnssStatus is null, that means that this method is called outside + // onGpsStatusChanged(). Return an empty status to maintain backwards compatibility. + if (mGnssStatus != null) { + status.setStatus(mGnssStatus, mTimeToFirstFix); + } + return status; + } + + /** + * Returns the system information of the GPS hardware. + * May return 0 if GPS hardware is earlier than 2016. + * @hide + */ + @TestApi + public int getGnssYearOfHardware() { + try { + return mService.getGnssYearOfHardware(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the batch size (in number of Location objects) that are supported by the batching + * interface. + * + * @return Maximum number of location objects that can be returned + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) + public int getGnssBatchSize() { + try { + return mService.getGnssBatchSize(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Start hardware-batching of GNSS locations. This API is primarily used when the AP is + * asleep and the device can batch GNSS locations in the hardware. + * + * Note this is designed (as was the fused location interface before it) for a single user + * SystemApi - requests are not consolidated. Care should be taken when the System switches + * users that may have different batching requests, to stop hardware batching for one user, and + * restart it for the next. + * + * @param periodNanos Time interval, in nanoseconds, that the GNSS locations are requested + * within the batch + * @param wakeOnFifoFull True if the hardware batching should flush the locations in a + * a callback to the listener, when it's internal buffer is full. If + * set to false, the oldest location information is, instead, + * dropped when the buffer is full. + * @param callback The listener on which to return the batched locations + * @param handler The handler on which to process the callback + * + * @return True if batching was successfully started + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) + public boolean registerGnssBatchedLocationCallback(long periodNanos, boolean wakeOnFifoFull, + BatchedLocationCallback callback, Handler handler) { + mBatchedLocationCallbackTransport.add(callback, handler); + + try { + return mService.startGnssBatch(periodNanos, wakeOnFifoFull, mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Flush the batched GNSS locations. + * All GNSS locations currently ready in the batch are returned via the callback sent in + * startGnssBatch(), and the buffer containing the batched locations is cleared. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) + public void flushGnssBatch() { + try { + mService.flushGnssBatch(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Stop batching locations. This API is primarily used when the AP is + * asleep and the device can batch locations in the hardware. + * + * @param callback the specific callback class to remove from the transport layer + * + * @return True if batching was successfully started + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.LOCATION_HARDWARE) + public boolean unregisterGnssBatchedLocationCallback(BatchedLocationCallback callback) { + + mBatchedLocationCallbackTransport.remove(callback); + + try { + return mService.stopGnssBatch(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sends additional commands to a location provider. + * Can be used to support provider specific extensions to the Location Manager API + * + * @param provider name of the location provider. + * @param command name of the command to send to the provider. + * @param extras optional arguments for the command (or null). + * The provider may optionally fill the extras Bundle with results from the command. + * + * @return true if the command succeeds. + */ + public boolean sendExtraCommand(String provider, String command, Bundle extras) { + try { + return mService.sendExtraCommand(provider, command, extras); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Used by NetInitiatedActivity to report user response + * for network initiated GPS fix requests. + * + * @hide + */ + public boolean sendNiResponse(int notifId, int userResponse) { + try { + return mService.sendNiResponse(notifId, userResponse); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static void checkProvider(String provider) { + if (provider == null) { + throw new IllegalArgumentException("invalid provider: " + provider); + } + } + + private static void checkCriteria(Criteria criteria) { + if (criteria == null) { + throw new IllegalArgumentException("invalid criteria: " + criteria); + } + } + + private static void checkListener(LocationListener listener) { + if (listener == null) { + throw new IllegalArgumentException("invalid listener: " + listener); + } + } + + private void checkPendingIntent(PendingIntent intent) { + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + intent); + } + if (!intent.isTargetedToPackage()) { + IllegalArgumentException e = new IllegalArgumentException( + "pending intent must be targeted to package"); + if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) { + throw e; + } else { + Log.w(TAG, e); + } + } + } + + private static void checkGeofence(Geofence fence) { + if (fence == null) { + throw new IllegalArgumentException("invalid geofence: " + fence); + } + } +} diff --git a/android/location/LocationProvider.java b/android/location/LocationProvider.java new file mode 100644 index 00000000..c4fd0975 --- /dev/null +++ b/android/location/LocationProvider.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2007 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 com.android.internal.location.ProviderProperties; + +/** + * An abstract superclass for location providers. A location provider + * provides periodic reports on the geographical location of the + * device. + * + * <p> Each provider has a set of criteria under which it may be used; + * for example, some providers require GPS hardware and visibility to + * a number of satellites; others require the use of the cellular + * radio, or access to a specific carrier's network, or to the + * internet. They may also have different battery consumption + * characteristics or monetary costs to the user. The {@link + * Criteria} class allows providers to be selected based on + * user-specified criteria. + */ +public class LocationProvider { + public static final int OUT_OF_SERVICE = 0; + public static final int TEMPORARILY_UNAVAILABLE = 1; + public static final int AVAILABLE = 2; + + /** + * A regular expression matching characters that may not appear + * in the name of a LocationProvider + * @hide + */ + public static final String BAD_CHARS_REGEX = "[^a-zA-Z0-9]"; + + private final String mName; + private final ProviderProperties mProperties; + + /** + * Constructs a LocationProvider with the given name. Provider names must + * consist only of the characters [a-zA-Z0-9]. + * + * @throws IllegalArgumentException if name contains an illegal character + * + * @hide + */ + public LocationProvider(String name, ProviderProperties properties) { + if (name.matches(BAD_CHARS_REGEX)) { + throw new IllegalArgumentException("provider name contains illegal character: " + name); + } + mName = name; + mProperties = properties; + } + + /** + * Returns the name of this provider. + */ + public String getName() { + return mName; + } + + /** + * Returns true if this provider meets the given criteria, + * false otherwise. + */ + public boolean meetsCriteria(Criteria criteria) { + return propertiesMeetCriteria(mName, mProperties, criteria); + } + + /** + * @hide + */ + public static boolean propertiesMeetCriteria(String name, ProviderProperties properties, + Criteria criteria) { + if (LocationManager.PASSIVE_PROVIDER.equals(name)) { + // passive provider never matches + return false; + } + if (properties == null) { + // unfortunately this can happen for provider in remote services + // that have not finished binding yet + return false; + } + + if (criteria.getAccuracy() != Criteria.NO_REQUIREMENT && + criteria.getAccuracy() < properties.mAccuracy) { + return false; + } + if (criteria.getPowerRequirement() != Criteria.NO_REQUIREMENT && + criteria.getPowerRequirement() < properties.mPowerRequirement) { + return false; + } + if (criteria.isAltitudeRequired() && !properties.mSupportsAltitude) { + return false; + } + if (criteria.isSpeedRequired() && !properties.mSupportsSpeed) { + return false; + } + if (criteria.isBearingRequired() && !properties.mSupportsBearing) { + return false; + } + if (!criteria.isCostAllowed() && properties.mHasMonetaryCost) { + return false; + } + return true; + } + + /** + * Returns true if the provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + public boolean requiresNetwork() { + return mProperties.mRequiresNetwork; + } + + /** + * Returns true if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + public boolean requiresSatellite() { + return mProperties.mRequiresSatellite; + } + + /** + * Returns true if the provider requires access to an appropriate + * cellular network (e.g., to make use of cell tower IDs), false + * otherwise. + */ + public boolean requiresCell() { + return mProperties.mRequiresCell; + } + + /** + * Returns true if the use of this provider may result in a + * monetary charge to the user, false if use is free. It is up to + * each provider to give accurate information. + */ + public boolean hasMonetaryCost() { + return mProperties.mHasMonetaryCost; + } + + /** + * Returns true if the provider is able to provide altitude + * information, false otherwise. A provider that reports altitude + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsAltitude() { + return mProperties.mSupportsAltitude; + } + + /** + * Returns true if the provider is able to provide speed + * information, false otherwise. A provider that reports speed + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsSpeed() { + return mProperties.mSupportsSpeed; + } + + /** + * Returns true if the provider is able to provide bearing + * information, false otherwise. A provider that reports bearing + * under most circumstances but may occassionally not report it + * should return true. + */ + public boolean supportsBearing() { + return mProperties.mSupportsBearing; + } + + /** + * Returns the power requirement for this provider. + * + * @return the power requirement for this provider, as one of the + * constants Criteria.POWER_REQUIREMENT_*. + */ + public int getPowerRequirement() { + return mProperties.mPowerRequirement; + } + + /** + * Returns a constant describing horizontal accuracy of this provider. + * If the provider returns finer grain or exact location, + * {@link Criteria#ACCURACY_FINE} is returned, otherwise if the + * location is only approximate then {@link Criteria#ACCURACY_COARSE} + * is returned. + */ + public int getAccuracy() { + return mProperties.mAccuracy; + } +} diff --git a/android/location/LocationRequest.java b/android/location/LocationRequest.java new file mode 100644 index 00000000..65e7cedf --- /dev/null +++ b/android/location/LocationRequest.java @@ -0,0 +1,672 @@ +/* + * 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}. + * + * <p>LocationRequest objects are used to request a quality of service + * for location updates from the Location Manager. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>Applications cannot specify the exact location sources that are + * used by Android's <em>Fusion Engine</em>. In fact, the system + * may have multiple location sources (providers) running and may + * fuse the results from several sources into a single Location object. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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). + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>{@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. + * + * <p>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. + * + * <p>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}. + * + * <p>Applications with only the coarse location permission may have their + * interval silently throttled. + * + * <p>An interval of 0 is allowed, but not recommended, since + * location updates may be extremely fast on future implementations. + * + * <p>{@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. + * + * <p>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). + * + * <p>This allows your application to passively acquire locations + * at a rate faster than it actively acquires locations, saving power. + * + * <p>Unlike {@link #setInterval}, this parameter is exact. Your + * application will never receive updates faster than this value. + * + * <p>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}). + * + * <p>An interval of 0 is allowed, but not recommended, since + * location updates may be extremely fast on future implementations. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>The location manager will automatically stop updates after + * the request expires. + * + * <p>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. + * + * <p>This expiration time uses the same time base as {@link SystemClock#elapsedRealtime}. + * + * <p>The location manager will automatically stop updates after + * the request expires. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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<LocationRequest> CREATOR = + new Parcelable.Creator<LocationRequest>() { + @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(); + } +} diff --git a/android/location/OnNmeaMessageListener.java b/android/location/OnNmeaMessageListener.java new file mode 100644 index 00000000..ccf6ce85 --- /dev/null +++ b/android/location/OnNmeaMessageListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 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; + +/** +* Used for receiving NMEA sentences from the GNSS. +* NMEA 0183 is a standard for communicating with marine electronic devices +* and is a common method for receiving data from a GNSS, typically over a serial port. +* See <a href="http://en.wikipedia.org/wiki/NMEA_0183">NMEA 0183</a> for more details. +* You can implement this interface and call {@link LocationManager#addNmeaListener} +* to receive NMEA data from the GNSS engine. +*/ +public interface OnNmeaMessageListener { + /** + * Called when an NMEA message is received. + * @param message NMEA message + * @param timestamp milliseconds since January 1, 1970. + */ + void onNmeaMessage(String message, long timestamp); +} diff --git a/android/location/SettingInjectorService.java b/android/location/SettingInjectorService.java new file mode 100644 index 00000000..fcd2cdec --- /dev/null +++ b/android/location/SettingInjectorService.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2013 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.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +/** + * Dynamically specifies the enabled status of a preference injected into + * the list of app settings displayed by the system settings app + * <p/> + * For use only by apps that are included in the system image, for preferences that affect multiple + * apps. Location settings that apply only to one app should be shown within that app, + * rather than in the system settings. + * <p/> + * To add a preference to the list, a subclass of {@link SettingInjectorService} must be declared in + * the manifest as so: + * + * <pre> + * <service android:name="com.example.android.injector.MyInjectorService" > + * <intent-filter> + * <action android:name="android.location.SettingInjectorService" /> + * </intent-filter> + * + * <meta-data + * android:name="android.location.SettingInjectorService" + * android:resource="@xml/my_injected_location_setting" /> + * </service> + * </pre> + * The resource file specifies the static data for the setting: + * <pre> + * <injected-location-setting xmlns:android="http://schemas.android.com/apk/res/android" + * android:title="@string/injected_setting_title" + * android:icon="@drawable/ic_acme_corp" + * android:settingsActivity="com.example.android.injector.MySettingActivity" + * /> + * </pre> + * Here: + * <ul> + * <li>title: The {@link android.preference.Preference#getTitle()} value. The title should make + * it clear which apps are affected by the setting, typically by including the name of the + * developer. For example, "Acme Corp. ads preferences." </li> + * + * <li>icon: The {@link android.preference.Preference#getIcon()} value. Typically this will be a + * generic icon for the developer rather than the icon for an individual app.</li> + * + * <li>settingsActivity: the activity which is launched to allow the user to modify the setting + * value. The activity must be in the same package as the subclass of + * {@link SettingInjectorService}. The activity should use your own branding to help emphasize + * to the user that it is not part of the system settings.</li> + * </ul> + * + * To ensure a good user experience, your {@link android.app.Application#onCreate()}, + * and {@link #onGetEnabled()} methods must all be fast. If either is slow, + * it can delay the display of settings values for other apps as well. Note further that these + * methods are called on your app's UI thread. + * <p/> + * For compactness, only one copy of a given setting should be injected. If each account has a + * distinct value for the setting, then only {@code settingsActivity} should display the value for + * each account. + */ +public abstract class SettingInjectorService extends Service { + + private static final String TAG = "SettingInjectorService"; + + /** + * Intent action that must be declared in the manifest for the subclass. Used to start the + * service to read the dynamic status for the setting. + */ + public static final String ACTION_SERVICE_INTENT = "android.location.SettingInjectorService"; + + /** + * Name of the meta-data tag used to specify the resource file that includes the settings + * attributes. + */ + public static final String META_DATA_NAME = "android.location.SettingInjectorService"; + + /** + * Name of the XML tag that includes the attributes for the setting. + */ + public static final String ATTRIBUTES_NAME = "injected-location-setting"; + + /** + * Intent action a client should broadcast when the value of one of its injected settings has + * changed, so that the setting can be updated in the UI. + */ + public static final String ACTION_INJECTED_SETTING_CHANGED = + "android.location.InjectedSettingChanged"; + + /** + * Name of the bundle key for the string specifying whether the setting is currently enabled. + * + * @hide + */ + public static final String ENABLED_KEY = "enabled"; + + /** + * Name of the intent key used to specify the messenger + * + * @hide + */ + public static final String MESSENGER_KEY = "messenger"; + + private final String mName; + + /** + * Constructor. + * + * @param name used to identify your subclass in log messages + */ + public SettingInjectorService(String name) { + mName = name; + } + + @Override + public final IBinder onBind(Intent intent) { + return null; + } + + @Override + public final void onStart(Intent intent, int startId) { + super.onStart(intent, startId); + } + + @Override + public final int onStartCommand(Intent intent, int flags, int startId) { + onHandleIntent(intent); + stopSelf(startId); + return START_NOT_STICKY; + } + + private void onHandleIntent(Intent intent) { + + boolean enabled; + try { + enabled = onGetEnabled(); + } catch (RuntimeException e) { + // Exception. Send status anyway, so that settings injector can immediately start + // loading the status of the next setting. + sendStatus(intent, true); + throw e; + } + + sendStatus(intent, enabled); + } + + /** + * Send the enabled values back to the caller via the messenger encoded in the + * intent. + */ + private void sendStatus(Intent intent, boolean enabled) { + Message message = Message.obtain(); + Bundle bundle = new Bundle(); + bundle.putBoolean(ENABLED_KEY, enabled); + message.setData(bundle); + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, mName + ": received " + intent + + ", enabled=" + enabled + ", sending message: " + message); + } + + Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY); + try { + messenger.send(message); + } catch (RemoteException e) { + Log.e(TAG, mName + ": sending dynamic status failed", e); + } + } + + /** + * This method is no longer called, because status values are no longer shown for any injected + * setting. + * + * @return ignored + * + * @deprecated not called any more + */ + @Deprecated + protected abstract String onGetSummary(); + + /** + * Returns the {@link android.preference.Preference#isEnabled()} value. Should not perform + * unpredictably-long operations such as network access--see the running-time comments in the + * class-level javadoc. + * <p/> + * Note that to prevent churn in the settings list, there is no support for dynamically choosing + * to hide a setting. Instead you should have this method return false, which will disable the + * setting and its link to your setting activity. One reason why you might choose to do this is + * if {@link android.provider.Settings.Secure#LOCATION_MODE} is {@link + * android.provider.Settings.Secure#LOCATION_MODE_OFF}. + * <p/> + * It is possible that the user may click on the setting before this method returns, so your + * settings activity must handle the case where it is invoked even though the setting is + * disabled. The simplest approach may be to simply call {@link android.app.Activity#finish()} + * when disabled. + * + * @return the {@link android.preference.Preference#isEnabled()} value + */ + protected abstract boolean onGetEnabled(); +} |