diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
tree | 35051494d2af230dce54d6b31c6af8fc24091316 /location/java | |
download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 |
Initial Contribution
Diffstat (limited to 'location/java')
61 files changed, 10190 insertions, 0 deletions
diff --git a/location/java/android/location/Address.aidl b/location/java/android/location/Address.aidl new file mode 100644 index 0000000..5be1498 --- /dev/null +++ b/location/java/android/location/Address.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable Address; diff --git a/location/java/android/location/Address.java b/location/java/android/location/Address.java new file mode 100644 index 0000000..3551363 --- /dev/null +++ b/location/java/android/location/Address.java @@ -0,0 +1,516 @@ +/* + * 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 addres 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 mThoroughfare; + 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 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 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.mThoroughfare = 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(mThoroughfare); + 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/location/java/android/location/Criteria.aidl b/location/java/android/location/Criteria.aidl new file mode 100644 index 0000000..b9a8879 --- /dev/null +++ b/location/java/android/location/Criteria.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable Criteria; diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java new file mode 100644 index 0000000..9d258d0 --- /dev/null +++ b/location/java/android/location/Criteria.java @@ -0,0 +1,242 @@ +/* + * 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; + + private int mAccuracy = NO_REQUIREMENT; + private int mPowerRequirement = NO_REQUIREMENT; +// private int mPreferredResponseTime = 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) { + mAccuracy = criteria.mAccuracy; + mPowerRequirement = criteria.mPowerRequirement; +// mPreferredResponseTime = criteria.mPreferredResponseTime; + mAltitudeRequired = criteria.mAltitudeRequired; + mBearingRequired = criteria.mBearingRequired; + mSpeedRequired = criteria.mSpeedRequired; + mCostAllowed = criteria.mCostAllowed; + } + + /** + * 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 usually consumes more power and may take + * longer. + * + * @throws IllegalArgumentException if accuracy is negative + */ + public void setAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT && accuracy > ACCURACY_COARSE) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mAccuracy = accuracy; + } + + /** + * 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() { + return mAccuracy; + } + + /** + * 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 the preferred response time of the provider, in milliseconds. +// */ +// public void setPreferredResponseTime(int time) { +// mPreferredResponseTime = time; +// } +// +// /** +// * Returns the preferred response time of the provider, in milliseconds. +// */ +// public int getPreferredResponseTime() { +// return mPreferredResponseTime; +// } + + /** + * 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>() { + public Criteria createFromParcel(Parcel in) { + Criteria c = new Criteria(); + c.mAccuracy = in.readInt(); + c.mPowerRequirement = in.readInt(); +// c.mPreferredResponseTime = in.readInt(); + c.mAltitudeRequired = in.readInt() != 0; + c.mBearingRequired = in.readInt() != 0; + c.mSpeedRequired = in.readInt() != 0; + c.mCostAllowed = in.readInt() != 0; + return c; + } + + public Criteria[] newArray(int size) { + return new Criteria[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mAccuracy); + parcel.writeInt(mPowerRequirement); +// parcel.writeInt(mPreferredResponseTime); + parcel.writeInt(mAltitudeRequired ? 1 : 0); + parcel.writeInt(mBearingRequired ? 1 : 0); + parcel.writeInt(mSpeedRequired ? 1 : 0); + parcel.writeInt(mCostAllowed ? 1 : 0); + } +} diff --git a/location/java/android/location/DummyLocationProvider.java b/location/java/android/location/DummyLocationProvider.java new file mode 100644 index 0000000..e1cd4e9 --- /dev/null +++ b/location/java/android/location/DummyLocationProvider.java @@ -0,0 +1,168 @@ +/* + * 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; + +/** + * A stub implementation of LocationProvider used by LocationManager. + * A DummyLocationProvider may be queried to determine the properties + * of the provider whcih it shadows, but does not actually provide location + * data. + * + * {@hide} + */ +class DummyLocationProvider extends LocationProvider { + + private static final String TAG = "DummyLocationProvider"; + + String mName; + boolean mRequiresNetwork; + boolean mRequiresSatellite; + boolean mRequiresCell; + boolean mHasMonetaryCost; + boolean mSupportsAltitude; + boolean mSupportsSpeed; + boolean mSupportsBearing; + int mPowerRequirement; + int mAccuracy; + + /* package */ DummyLocationProvider(String name) { + super(name); + } + + public void setRequiresNetwork(boolean requiresNetwork) { + mRequiresNetwork = requiresNetwork; + } + + public void setRequiresSatellite(boolean requiresSatellite) { + mRequiresSatellite = requiresSatellite; + } + + public void setRequiresCell(boolean requiresCell) { + mRequiresCell = requiresCell; + } + + public void setHasMonetaryCost(boolean hasMonetaryCost) { + mHasMonetaryCost = hasMonetaryCost; + } + + public void setSupportsAltitude(boolean supportsAltitude) { + mSupportsAltitude = supportsAltitude; + } + + public void setSupportsSpeed(boolean supportsSpeed) { + mSupportsSpeed = supportsSpeed; + } + + public void setSupportsBearing(boolean supportsBearing) { + mSupportsBearing = supportsBearing; + } + + public void setPowerRequirement(int powerRequirement) { + mPowerRequirement = powerRequirement; + } + + public void setAccuracy(int accuracy) { + mAccuracy = accuracy; + } + + /** + * Returns true if the provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + public boolean requiresNetwork() { + return mRequiresNetwork; + } + + /** + * Returns true if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + public boolean requiresSatellite() { + return 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 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 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 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 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 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 mPowerRequirement; + } + + /** + * Returns a constant describing the horizontal accuracy returned + * by this provider. + * + * @return the horizontal accuracy for this provider, as one of the + * constants Criteria.ACCURACY_*. + */ + public int getAccuracy() { + return mAccuracy; + } +} + diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java new file mode 100644 index 0000000..92a19e4 --- /dev/null +++ b/location/java/android/location/Geocoder.java @@ -0,0 +1,242 @@ +/* + * 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. + */ +public final class Geocoder { + private static final String TAG = "Geocoder"; + + private String mLanguage; + private String mCountry; + private String mVariant; + private String mAppName; + private ILocationManager mService; + + /** + * 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"); + } + mLanguage = locale.getLanguage(); + mCountry = locale.getCountry(); + mVariant = locale.getVariant(); + mAppName = context.getPackageName(); + + 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 or null if no matches were + * found. + * + * @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, + mLanguage, mCountry, mVariant, mAppName, 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 or null if no matches were found. + * + * @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, mLanguage, mCountry, mVariant, mAppName, 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 or null if no matches were found. + * + * @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, mLanguage, mCountry, mVariant, mAppName, new ArrayList<Address>()); + 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/location/java/android/location/GpsStatusListener.java b/location/java/android/location/GpsStatusListener.java new file mode 100644 index 0000000..e494887 --- /dev/null +++ b/location/java/android/location/GpsStatusListener.java @@ -0,0 +1,58 @@ +/* + * 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; + +/** + * Used for receiving notifications from the SensorManager when + * sensor values have changed. + * + * @hide + */ +public interface GpsStatusListener { + + /** + * Called when the GPS has started. + */ + void onGpsStarted(); + + /** + * Called when the GPS has stopped. + */ + void onGpsStopped(); + + /** + * Called when the GPS status has received its first fix since starting. + * + * @param ttff Time to first fix in milliseconds. + */ + void onFirstFix(int ttff); + + /** + * Called when the GPS SV status has changed. + * + * @param svCount The number of visible SVs + * @param prns Array of SV prns. Length of array is svCount. + * @param snrs Array of signal to noise ratios for SVs, in 1/10 dB units. Length of array is svCount. + * @param elevations Array of SV elevations in degrees. Length of array is svCount. + * @param azimuths Array of SV azimuths in degrees. Length of array is svCount. + * @param ephemerisMask Bit mask indicating which SVs the GPS has ephemeris data for. + * @param almanacMask Bit mask indicating which SVs the GPS has almanac data for. + * @param usedInFixMask Bit mask indicating which SVs were used in the most recent GPS fix. + */ + public void onSvStatusChanged(int svCount, int[] prns, float[] snrs, float[] elevations, + float[] azimuths, int ephemerisMask, int almanacMask, int usedInFixMask); +} diff --git a/location/java/android/location/IGpsStatusListener.aidl b/location/java/android/location/IGpsStatusListener.aidl new file mode 100644 index 0000000..5dc0fe8 --- /dev/null +++ b/location/java/android/location/IGpsStatusListener.aidl @@ -0,0 +1,32 @@ +/* + * 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.location.Location; + +/** + * {@hide} + */ +oneway interface IGpsStatusListener +{ + void onGpsStarted(); + void onGpsStopped(); + void onFirstFix(int ttff); + void onSvStatusChanged(int svCount, in int[] prns, in float[] snrs, + in float[] elevations, in float[] azimuths, + int ephemerisMask, int almanacMask, int usedInFixMask); +} diff --git a/location/java/android/location/ILocationListener.aidl b/location/java/android/location/ILocationListener.aidl new file mode 100644 index 0000000..7627cf6 --- /dev/null +++ b/location/java/android/location/ILocationListener.aidl @@ -0,0 +1,32 @@ +/* //device/java/android/android/location/ILocationListener.aidl +** +** Copyright 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.location.Location; +import android.os.Bundle; + +/** + * {@hide} + */ +oneway interface ILocationListener +{ + void onLocationChanged(in Location location); + void onStatusChanged(String provider, int status, in Bundle extras); + void onProviderEnabled(String provider); + void onProviderDisabled(String provider); +} diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl new file mode 100644 index 0000000..d3eefeb --- /dev/null +++ b/location/java/android/location/ILocationManager.aidl @@ -0,0 +1,73 @@ +/* + * 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.app.PendingIntent; +import android.location.Address; +import android.location.IGpsStatusListener; +import android.location.ILocationListener; +import android.location.Location; +import android.os.Bundle; + +/** + * System private API for talking with the location service. + * + * {@hide} + */ +interface ILocationManager +{ + List getAllProviders(); + List getProviders(boolean enabledOnly); + + void updateProviders(); + + void requestLocationUpdates(String provider, long minTime, float minDistance, + in ILocationListener listener); + void removeUpdates(in ILocationListener listener); + + boolean addGpsStatusListener(IGpsStatusListener listener); + void removeGpsStatusListener(IGpsStatusListener listener); + + boolean sendExtraCommand(String provider, String command, inout Bundle extras); + + void addProximityAlert(double latitude, double longitude, float distance, + long expiration, in PendingIntent intent); + void removeProximityAlert(in PendingIntent intent); + + Bundle getProviderInfo(String provider); + boolean isProviderEnabled(String provider); + + Location getLastKnownLocation(String provider); + + String getFromLocation(double latitude, double longitude, int maxResults, + String language, String country, String variant, String appName, out List<Address> addrs); + String getFromLocationName(String locationName, + double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude, int maxResults, + String language, String country, String variant, String appName, out List<Address> addrs); + + void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy); + void removeTestProvider(String provider); + void setTestProviderLocation(String provider, in Location loc); + void clearTestProviderLocation(String provider); + void setTestProviderEnabled(String provider, boolean enabled); + void clearTestProviderEnabled(String provider); + void setTestProviderStatus(String provider, int status, in Bundle extras, long updateTime); + void clearTestProviderStatus(String provider); +} diff --git a/location/java/android/location/Location.aidl b/location/java/android/location/Location.aidl new file mode 100644 index 0000000..f47b488 --- /dev/null +++ b/location/java/android/location/Location.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable Location; diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java new file mode 100644 index 0000000..d381f6e --- /dev/null +++ b/location/java/android/location/Location.java @@ -0,0 +1,730 @@ +/* + * 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.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.text.DecimalFormat; +import java.util.StringTokenizer; + +/** + * A class representing a geographic location sensed at a particular + * time (a "fix"). A location consists of a latitude and longitude, a + * UTC timestamp. and optionally information on altitude, speed, and + * bearing. + * + * <p> Information specific to a particular provider or class of + * providers may be communicated to the application using getExtras, + * which returns a Bundle of key/value pairs. Each provider will only + * provide those entries for which information is available. + */ +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; + + private String mProvider; + private long mTime = 0; + private double mLatitude = 0.0; + private double mLongitude = 0.0; + private boolean mHasAltitude = false; + private double mAltitude = 0.0f; + private boolean mHasSpeed = false; + private float mSpeed = 0.0f; + private boolean mHasBearing = false; + private float mBearing = 0.0f; + private boolean mHasAccuracy = false; + private float mAccuracy = 0.0f; + private Bundle mExtras = null; + + // Cache the inputs and outputs of computeDistanceAndBearing + // so calls to distanceTo() and bearingTo() can share work + 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; + // Scratchpad + private float[] mResults = new float[2]; + + /** + * Constructs a new Location. By default, time, latitude, + * longitude, and numSatellites are 0; hasAltitude, hasSpeed, and + * hasBearing are false; and there is no extra information. + * + * @param provider the name of the location provider that generated this + * location fix. + */ + public Location(String provider) { + mProvider = provider; + } + + /** + * Constructs a new Location object that is a copy of the given + * location. + */ + 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; + mLatitude = l.mLatitude; + mLongitude = l.mLongitude; + mHasAltitude = l.mHasAltitude; + mAltitude = l.mAltitude; + mHasSpeed = l.mHasSpeed; + mSpeed = l.mSpeed; + mHasBearing = l.mHasBearing; + mBearing = l.mBearing; + mHasAccuracy = l.mHasAccuracy; + mAccuracy = l.mAccuracy; + mExtras = (l.mExtras == null) ? null : new Bundle(l.mExtras); + } + + /** + * Clears the contents of the location. + */ + public void reset() { + mProvider = null; + mTime = 0; + mLatitude = 0; + mLongitude = 0; + mHasAltitude = false; + mAltitude = 0; + mHasSpeed = false; + mSpeed = 0; + mHasBearing = false; + mBearing = 0; + mHasAccuracy = false; + mAccuracy = 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. + * + * @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. + * + * @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; + + if (st.hasMoreTokens()) { + min = Integer.parseInt(minutes); + String seconds = st.nextToken(); + sec = Double.parseDouble(seconds); + } 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); + } + if (min < 0 || min > 59) { + throw new IllegalArgumentException("coordinate=" + + coordinate); + } + if (sec < 0 || sec > 59) { + 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, float[] 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[0] = distance; + if (results.length > 1) { + float initialBearing = (float) Math.atan2(cosU2 * sinLambda, + cosU1 * sinU2 - sinU1 * cosU2 * cosLambda); + initialBearing *= 180.0 / Math.PI; + results[1] = initialBearing; + if (results.length > 2) { + float finalBearing = (float) Math.atan2(cosU1 * sinLambda, + -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda); + finalBearing *= 180.0 / Math.PI; + results[2] = finalBearing; + } + } + } + + /** + * 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"); + } + computeDistanceAndBearing(startLatitude, startLongitude, + endLatitude, endLongitude, results); + } + + /** + * 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) { + // See if we already have the result + synchronized (mResults) { + if (mLatitude != mLat1 || mLongitude != mLon1 || + dest.mLatitude != mLat2 || dest.mLongitude != mLon2) { + computeDistanceAndBearing(mLatitude, mLongitude, + dest.mLatitude, dest.mLongitude, mResults); + mLat1 = mLatitude; + mLon1 = mLongitude; + mLat2 = dest.mLatitude; + mLon2 = dest.mLongitude; + mDistance = mResults[0]; + mInitialBearing = mResults[1]; + } + return 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) { + synchronized (mResults) { + // See if we already have the result + if (mLatitude != mLat1 || mLongitude != mLon1 || + dest.mLatitude != mLat2 || dest.mLongitude != mLon2) { + computeDistanceAndBearing(mLatitude, mLongitude, + dest.mLatitude, dest.mLongitude, mResults); + mLat1 = mLatitude; + mLon1 = mLongitude; + mLat2 = dest.mLatitude; + mLon2 = dest.mLongitude; + mDistance = mResults[0]; + mInitialBearing = mResults[1]; + } + return mInitialBearing; + } + } + + /** + * Returns the name of the provider that generated this fix, + * or null if it is not associated with a provider. + */ + public String getProvider() { + return mProvider; + } + + /** + * Sets the name of the provider that generated this fix. + */ + public void setProvider(String provider) { + mProvider = provider; + } + + /** + * Returns the UTC time of this fix, in milliseconds since January 1, + * 1970. + */ + public long getTime() { + return mTime; + } + + /** + * Sets the UTC time of this fix, in milliseconds since January 1, + * 1970. + */ + public void setTime(long time) { + mTime = time; + } + + /** + * Returns the latitude of this fix. + */ + public double getLatitude() { + return mLatitude; + } + + /** + * Sets the latitude of this fix. + */ + public void setLatitude(double latitude) { + mLatitude = latitude; + } + + /** + * Returns the longitude of this fix. + */ + public double getLongitude() { + return mLongitude; + } + + /** + * Sets the longitude of this fix. + */ + public void setLongitude(double longitude) { + mLongitude = longitude; + } + + /** + * Returns true if this fix contains altitude information, false + * otherwise. + */ + public boolean hasAltitude() { + return mHasAltitude; + } + + /** + * Returns the altitude of this fix. If {@link #hasAltitude} is false, + * 0.0f is returned. + */ + public double getAltitude() { + return mAltitude; + } + + /** + * Sets the altitude of this fix. Following this call, + * hasAltitude() will return true. + */ + public void setAltitude(double altitude) { + mAltitude = altitude; + mHasAltitude = true; + } + + /** + * Clears the altitude of this fix. Following this call, + * hasAltitude() will return false. + */ + public void removeAltitude() { + mAltitude = 0.0f; + mHasAltitude = false; + } + + /** + * Returns true if this fix contains speed information, false + * otherwise. The default implementation returns false. + */ + public boolean hasSpeed() { + return mHasSpeed; + } + + /** + * Returns the speed of the device over ground in meters/second. + * If hasSpeed() is false, 0.0f is returned. + */ + public float getSpeed() { + return mSpeed; + } + + /** + * Sets the speed of this fix, in meters/second. Following this + * call, hasSpeed() will return true. + */ + public void setSpeed(float speed) { + mSpeed = speed; + mHasSpeed = true; + } + + /** + * Clears the speed of this fix. Following this call, hasSpeed() + * will return false. + */ + public void removeSpeed() { + mSpeed = 0.0f; + mHasSpeed = false; + } + + /** + * Returns true if the provider is able to report bearing information, + * false otherwise. The default implementation returns false. + */ + public boolean hasBearing() { + return mHasBearing; + } + + /** + * Returns the direction of travel in degrees East of true + * North. If hasBearing() is false, 0.0 is returned. + */ + public float getBearing() { + return mBearing; + } + + /** + * Sets the bearing of this fix. Following this call, hasBearing() + * will return true. + */ + public void setBearing(float bearing) { + while (bearing < 0.0f) { + bearing += 360.0f; + } + while (bearing >= 360.0f) { + bearing -= 360.0f; + } + mBearing = bearing; + mHasBearing = true; + } + + /** + * Clears the bearing of this fix. Following this call, hasBearing() + * will return false. + */ + public void removeBearing() { + mBearing = 0.0f; + mHasBearing = false; + } + + /** + * Returns true if the provider is able to report accuracy information, + * false otherwise. The default implementation returns false. + */ + public boolean hasAccuracy() { + return mHasAccuracy; + } + + /** + * Returns the accuracy of the fix in meters. If hasAccuracy() is false, + * 0.0 is returned. + */ + public float getAccuracy() { + return mAccuracy; + } + + /** + * Sets the accuracy of this fix. Following this call, hasAccuracy() + * will return true. + */ + public void setAccuracy(float accuracy) { + mAccuracy = accuracy; + mHasAccuracy = true; + } + + /** + * Clears the accuracy of this fix. Following this call, hasAccuracy() + * will return false. + */ + public void removeAccuracy() { + mAccuracy = 0.0f; + mHasAccuracy = false; + } + + /** + * 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() { + return "Location[mProvider=" + mProvider + + ",mTime=" + mTime + + ",mLatitude=" + mLatitude + + ",mLongitude=" + mLongitude + + ",mHasAltitude=" + mHasAltitude + + ",mAltitude=" + mAltitude + + ",mHasSpeed=" + mHasSpeed + + ",mSpeed=" + mSpeed + + ",mHasBearing=" + mHasBearing + + ",mBearing=" + mBearing + + ",mHasAccuracy=" + mHasAccuracy + + ",mAccuracy=" + mAccuracy + + ",mExtras=" + mExtras + "]"; + } + + public static final Parcelable.Creator<Location> CREATOR = + new Parcelable.Creator<Location>() { + public Location createFromParcel(Parcel in) { + String provider = in.readString(); + Location l = new Location(provider); + l.mTime = in.readLong(); + l.mLatitude = in.readDouble(); + l.mLongitude = in.readDouble(); + l.mHasAltitude = in.readInt() != 0; + l.mAltitude = in.readDouble(); + l.mHasSpeed = in.readInt() != 0; + l.mSpeed = in.readFloat(); + l.mHasBearing = in.readInt() != 0; + l.mBearing = in.readFloat(); + l.mHasAccuracy = in.readInt() != 0; + l.mAccuracy = in.readFloat(); + l.mExtras = in.readBundle(); + return l; + } + + public Location[] newArray(int size) { + return new Location[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mProvider); + parcel.writeLong(mTime); + parcel.writeDouble(mLatitude); + parcel.writeDouble(mLongitude); + parcel.writeInt(mHasAltitude ? 1 : 0); + parcel.writeDouble(mAltitude); + parcel.writeInt(mHasSpeed ? 1 : 0); + parcel.writeFloat(mSpeed); + parcel.writeInt(mHasBearing ? 1 : 0); + parcel.writeFloat(mBearing); + parcel.writeInt(mHasAccuracy ? 1 : 0); + parcel.writeFloat(mAccuracy); + parcel.writeBundle(mExtras); + } +} diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java new file mode 100644 index 0000000..0f5f388 --- /dev/null +++ b/location/java/android/location/LocationListener.java @@ -0,0 +1,82 @@ +/* + * 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. + */ +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/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java new file mode 100644 index 0000000..b5adb33 --- /dev/null +++ b/location/java/android/location/LocationManager.java @@ -0,0 +1,1179 @@ +/* + * 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.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.os.Looper; +import android.os.RemoteException; +import android.os.Handler; +import android.os.Message; +import android.util.Config; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +/** + * 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>You do not + * instantiate this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService + * Context.getSystemService(Context.LOCATION_SERVICE)}. + */ +public class LocationManager { + private static final String TAG = "LocationManager"; + private ILocationManager mService; + private HashMap<GpsStatusListener, GpsStatusListenerTransport> mGpsStatusListeners = + new HashMap<GpsStatusListener, GpsStatusListenerTransport>(); + + /** + * Name of the network location provider. This provider determines location based on + * availability of cell tower and WiFi access points. Results are retrieved + * by means of a network lookup. + * + * Requires either of the permissions android.permission.ACCESS_COARSE_LOCATION + * or android.permission.ACCESS_FINE_LOCATION. + */ + public static final String NETWORK_PROVIDER = "network"; + + /** + * Name of the GPS location provider. This provider determines location using + * satellites. Depending on conditions, this provider may take a while to return + * a location fix. + * + * Requires the permission android.permissions.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"; + + /** + * 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"; + + /** @hide -- does this belong here? */ + public static final String PROVIDER_DIR = "/data/location"; + + /** @hide */ + public static final String SYSTEM_DIR = "/data/system/location"; + + // 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); + } + }; + } + } + + public void onLocationChanged(Location location) { + Message msg = Message.obtain(); + msg.what = TYPE_LOCATION_CHANGED; + msg.obj = location; + mListenerHandler.sendMessage(msg); + } + + 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); + } + + public void onProviderEnabled(String provider) { + Message msg = Message.obtain(); + msg.what = TYPE_PROVIDER_ENABLED; + msg.obj = provider; + mListenerHandler.sendMessage(msg); + } + + 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; + } + } + } + /** + * @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(ILocationManager service) { + if (Config.LOGD) { + Log.d(TAG, "Constructor: service = " + service); + } + mService = service; + } + + private LocationProvider createProvider(String name, Bundle info) { + DummyLocationProvider provider = + new DummyLocationProvider(name); + provider.setRequiresNetwork(info.getBoolean("network")); + provider.setRequiresSatellite(info.getBoolean("satellite")); + provider.setRequiresCell(info.getBoolean("cell")); + provider.setHasMonetaryCost(info.getBoolean("cost")); + provider.setSupportsAltitude(info.getBoolean("altitude")); + provider.setSupportsSpeed(info.getBoolean("speed")); + provider.setSupportsBearing(info.getBoolean("bearing")); + provider.setPowerRequirement(info.getInt("power")); + provider.setAccuracy(info.getInt("accuracy")); + return provider; + } + + /** + * Returns a list of the names of all known location providers. 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 providers + */ + public List<String> getAllProviders() { + if (Config.LOGD) { + Log.d(TAG, "getAllProviders"); + } + try { + return mService.getAllProviders(); + } catch (RemoteException ex) { + Log.e(TAG, "getAllProviders: RemoteException", ex); + } + return null; + } + + /** + * Returns a list of the names of location providers. Only providers that + * are permitted to be accessed by the calling activity will be returned. + * + * @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(enabledOnly); + } catch (RemoteException ex) { + Log.e(TAG, "getProviders: RemoteException", ex); + } + return null; + } + + /** + * 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 + * @throws SecurityException if the caller is not permitted to access the + * given provider. + */ + public LocationProvider getProvider(String name) { + if (name == null) { + throw new IllegalArgumentException("name==null"); + } + try { + Bundle info = mService.getProviderInfo(name); + if (info == null) { + return null; + } + return createProvider(name, info); + } catch (RemoteException ex) { + Log.e(TAG, "getProvider: RemoteException", ex); + } + return null; + } + + /** + * 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) { + List<String> goodProviders = Collections.emptyList(); + List<String> providers = getProviders(enabledOnly); + for (String providerName : providers) { + LocationProvider provider = getProvider(providerName); + if (provider.meetsCriteria(criteria)) { + if (goodProviders.isEmpty()) { + goodProviders = new ArrayList<String>(); + } + goodProviders.add(providerName); + } + } + return goodProviders; + } + + /** + * Propagates the enabled/disabled state of the providers from the system + * settings to the providers themselves. + * + * {@hide} + */ + public void updateProviders() { + try { + mService.updateProviders(); + } catch (RemoteException ex) { + Log.e(TAG, "updateProviders: RemoteException", ex); + } + } + + /** + * Returns the next looser power requirement, in the sequence: + * + * POWER_LOW -> POWER_MEDIUM -> POWER_HIGH -> NO_REQUIREMENT + */ + private int nextPower(int power) { + switch (power) { + case Criteria.POWER_LOW: + return Criteria.POWER_MEDIUM; + case Criteria.POWER_MEDIUM: + return Criteria.POWER_HIGH; + case Criteria.POWER_HIGH: + return Criteria.NO_REQUIREMENT; + case Criteria.NO_REQUIREMENT: + default: + return Criteria.NO_REQUIREMENT; + } + } + + /** + * Returns the next looser accuracy requirement, in the sequence: + * + * ACCURACY_FINE -> ACCURACY_APPROXIMATE-> NO_REQUIREMENT + */ + private int nextAccuracy(int accuracy) { + if (accuracy == Criteria.ACCURACY_FINE) { + return Criteria.ACCURACY_COARSE; + } else { + return Criteria.NO_REQUIREMENT; + } + } + + private abstract class LpComparator implements Comparator<LocationProvider> { + + public int compare(int a1, int a2) { + if (a1 < a2) { + return -1; + } else if (a1 > a2) { + return 1; + } else { + return 0; + } + } + + public int compare(float a1, float a2) { + if (a1 < a2) { + return -1; + } else if (a1 > a2) { + return 1; + } else { + return 0; + } + } + } + + private class LpPowerComparator extends LpComparator { + public int compare(LocationProvider l1, LocationProvider l2) { + int a1 = l1.getPowerRequirement(); + int a2 = l2.getPowerRequirement(); + return compare(a1, a2); // Smaller is better + } + + public boolean equals(LocationProvider l1, LocationProvider l2) { + int a1 = l1.getPowerRequirement(); + int a2 = l2.getPowerRequirement(); + return a1 == a2; + } + } + + private class LpAccuracyComparator extends LpComparator { + public int compare(LocationProvider l1, LocationProvider l2) { + int a1 = l1.getAccuracy(); + int a2 = l2.getAccuracy(); + return compare(a1, a2); // Smaller is better + } + + public boolean equals(LocationProvider l1, LocationProvider l2) { + int a1 = l1.getAccuracy(); + int a2 = l2.getAccuracy(); + return a1 == a2; + } + } + + private class LpCapabilityComparator extends LpComparator { + + private static final int ALTITUDE_SCORE = 4; + private static final int BEARING_SCORE = 4; + private static final int SPEED_SCORE = 4; + + private int score(LocationProvider p) { + return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) + + (p.supportsBearing() ? BEARING_SCORE : 0) + + (p.supportsSpeed() ? SPEED_SCORE : 0); + } + + public int compare(LocationProvider l1, LocationProvider l2) { + int a1 = score(l1); + int a2 = score(l2); + return compare(-a1, -a2); // Bigger is better + } + + public boolean equals(LocationProvider l1, LocationProvider l2) { + int a1 = score(l1); + int a2 = score(l2); + return a1 == a2; + } + } + + private LocationProvider best(List<String> providerNames) { + List<LocationProvider> providers = new ArrayList<LocationProvider>(providerNames.size()); + for (String name : providerNames) { + providers.add(getProvider(name)); + } + + if (providers.size() < 2) { + return providers.get(0); + } + + // First, sort by power requirement + Collections.sort(providers, new LpPowerComparator()); + int power = providers.get(0).getPowerRequirement(); + if (power < providers.get(1).getPowerRequirement()) { + return providers.get(0); + } + + int idx, size; + + List<LocationProvider> tmp = new ArrayList<LocationProvider>(); + idx = 0; + size = providers.size(); + while ((idx < size) && (providers.get(idx).getPowerRequirement() == power)) { + tmp.add(providers.get(idx)); + idx++; + } + + // Next, sort by accuracy + Collections.sort(tmp, new LpAccuracyComparator()); + int acc = tmp.get(0).getAccuracy(); + if (acc < tmp.get(1).getAccuracy()) { + return tmp.get(0); + } + + List<LocationProvider> tmp2 = new ArrayList<LocationProvider>(); + idx = 0; + size = tmp.size(); + while ((idx < size) && (tmp.get(idx).getAccuracy() == acc)) { + tmp2.add(tmp.get(idx)); + idx++; + } + + // Finally, sort by capability "score" + Collections.sort(tmp2, new LpCapabilityComparator()); + return tmp2.get(0); + } + + /** + * 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) { + List<String> goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Make a copy of the criteria that we can modify + criteria = new Criteria(criteria); + + // Loosen power requirement + int power = criteria.getPowerRequirement(); + while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) { + power = nextPower(power); + criteria.setPowerRequirement(power); + goodProviders = getProviders(criteria, enabledOnly); + } + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + +// // Loosen response time requirement +// int responseTime = criteria.getPreferredResponseTime(); +// while (goodProviders.isEmpty() && +// (responseTime != Criteria.NO_REQUIREMENT)) { +// responseTime += 1000; +// if (responseTime > 60000) { +// responseTime = Criteria.NO_REQUIREMENT; +// } +// criteria.setPreferredResponseTime(responseTime); +// goodProviders = getProviders(criteria); +// } +// if (!goodProviders.isEmpty()) { +// return best(goodProviders); +// } + + // Loosen accuracy requirement + int accuracy = criteria.getAccuracy(); + while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) { + accuracy = nextAccuracy(accuracy); + criteria.setAccuracy(accuracy); + goodProviders = getProviders(criteria, enabledOnly); + } + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove bearing requirement + criteria.setBearingRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove speed requirement + criteria.setSpeedRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove altitude requirement + criteria.setAltitudeRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + return null; + } + + /** + * Registers the current activity to be notified periodically by + * the named provider. Periodically, the supplied LocationListener will + * be called with the current Location or with status updates. + * + * <p> It may take a while to receive the most recent location. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * <p> In case the provider is disabled by the user, updates will stop, + * and the {@link LocationListener#onProviderDisabled(String)} + * method will be called. As soon as the provider is enabled again, + * the {@link LocationListener#onProviderEnabled(String)} method will + * be called and location updates will start again. + * + * <p> The frequency of notification may be controlled using the + * minTime and minDistance parameters. If minTime is greater than 0, + * the LocationManager could potentially rest for minTime milliseconds + * between location updates to conserve power. If minDistance is greater than 0, + * a location will only be broadcasted if the device moves by minDistance meters. + * To obtain notifications as frequently as possible, set both parameters to 0. + * + * <p> Background services should be careful about setting a sufficiently high + * minTime so that the device doesn't consume too much power by keeping the + * GPS or wireless radios on all the time. In particular, values under 60000ms + * are not recommended. + * + * <p> The calling thread must be a {@link android.os.Looper} thread such as + * the main thread of the calling Activity. + * + * @param provider the name of the provider with which to register + * @param minTime the minimum time interval for notifications, in + * milliseconds. This field is only used as a hint to conserve power, and actual + * time between location updates may be greater or lesser than this value. + * @param minDistance the minimum distance interval for notifications, + * 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 + * @throws IllegalArgumentException if listener is null + * @throws RuntimeException if the calling thread has no Looper + * @throws SecurityException if no suitable permission is present for the provider. + */ + public void requestLocationUpdates(String provider, + long minTime, float minDistance, LocationListener listener) { + if (provider == null) { + throw new IllegalArgumentException("provider==null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener==null"); + } + _requestLocationUpdates(provider, minTime, minDistance, listener, null); + } + + /** + * Registers the current activity to be notified periodically by + * the named provider. Periodically, the supplied LocationListener will + * be called with the current Location or with status updates. + * + * <p> It may take a while to receive the most recent location. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * <p> In case the provider is disabled by the user, updates will stop, + * and the {@link LocationListener#onProviderDisabled(String)} + * method will be called. As soon as the provider is enabled again, + * the {@link LocationListener#onProviderEnabled(String)} method will + * be called and location updates will start again. + * + * <p> The frequency of notification may be controlled using the + * minTime and minDistance parameters. If minTime is greater than 0, + * the LocationManager could potentially rest for minTime milliseconds + * between location updates to conserve power. If minDistance is greater than 0, + * a location will only be broadcasted if the device moves by minDistance meters. + * To obtain notifications as frequently as possible, set both parameters to 0. + * + * <p> Background services should be careful about setting a sufficiently high + * minTime so that the device doesn't consume too much power by keeping the + * GPS or wireless radios on all the time. In particular, values under 60000ms + * are not recommended. + * + * <p> The supplied Looper is used to implement the callback mechanism. + * + * @param provider the name of the provider with which to register + * @param minTime the minimum time interval for notifications, in + * milliseconds. This field is only used as a hint to conserve power, and actual + * time between location updates may be greater or lesser than this value. + * @param minDistance the minimum distance interval for notifications, + * 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. + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if listener is null + * @throws IllegalArgumentException if looper is null + * @throws SecurityException if no suitable permission is present for the provider. + */ + public void requestLocationUpdates(String provider, + long minTime, float minDistance, LocationListener listener, + Looper looper) { + if (provider == null) { + throw new IllegalArgumentException("provider==null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener==null"); + } + if (looper == null) { + throw new IllegalArgumentException("looper==null"); + } + _requestLocationUpdates(provider, minTime, minDistance, listener, looper); + } + + private void _requestLocationUpdates(String provider, + long minTime, float minDistance, LocationListener listener, + Looper looper) { + if (minTime < 0L) { + minTime = 0L; + } + if (minDistance < 0.0f) { + minDistance = 0.0f; + } + + try { + synchronized (mListeners) { + ListenerTransport transport = mListeners.get(listener); + if (transport == null) { + transport = new ListenerTransport(listener, looper); + mListeners.put(listener, transport); + } + mListeners.put(listener, transport); + mService.requestLocationUpdates(provider, minTime, minDistance, + transport); + } + } catch (RemoteException ex) { + Log.e(TAG, "requestLocationUpdates: DeadObjectException", ex); + } + } + + /** + * Removes any current registration for location updates of the current activity + * with the given LocationListener. Following this call, updates will no longer + * occur for this listener. + * + * @param listener {#link LocationListener} object that no longer needs location updates + * @throws IllegalArgumentException if listener is null + */ + public void removeUpdates(LocationListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener==null"); + } + if (Config.LOGD) { + Log.d(TAG, "removeUpdates: listener = " + listener); + } + try { + ListenerTransport transport = mListeners.remove(listener); + if (transport != null) { + mService.removeUpdates(transport); + } + } catch (RemoteException ex) { + Log.e(TAG, "removeUpdates: DeadObjectException", ex); + } + } + + /** + * Sets a proximity alert for the location given by the position + * (latitude, longitude) and the given radius. 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> In case the screen goes to sleep, checks for proximity alerts + * happen only once every 4 minutes. This conserves battery life by + * ensuring that the device isn't perpetually awake. + * + * <p> Internally, this method uses both {@link #NETWORK_PROVIDER} + * and {@link #GPS_PROVIDER}. + * + * @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 no permission exists for the required + * providers. + */ + public void addProximityAlert(double latitude, double longitude, + float radius, long expiration, PendingIntent intent) { + if (Config.LOGD) { + Log.d(TAG, "addProximityAlert: latitude = " + latitude + + ", longitude = " + longitude + ", radius = " + radius + + ", expiration = " + expiration + + ", intent = " + intent); + } + try { + mService.addProximityAlert(latitude, longitude, radius, + expiration, intent); + } catch (RemoteException ex) { + Log.e(TAG, "addProximityAlert: RemoteException", ex); + } + } + + /** + * Removes the proximity alert with the given PendingIntent. + * + * @param intent the PendingIntent that no longer needs to be notified of + * proximity alerts + */ + public void removeProximityAlert(PendingIntent intent) { + if (Config.LOGD) { + Log.d(TAG, "removeProximityAlert: intent = " + intent); + } + try { + mService.removeProximityAlert(intent); + } catch (RemoteException ex) { + Log.e(TAG, "removeProximityAlert: RemoteException", ex); + } + } + + /** + * Returns the current enabled/disabled status of the given provider. If the + * user has enabled this provider in the Settings menu, true is returned + * otherwise false is returned + * + * @param provider the name of the provider + * @return true if the provider is enabled + * + * @throws SecurityException if no suitable permission is present for the provider. + * @throws IllegalArgumentException if provider is null or doesn't exist + */ + public boolean isProviderEnabled(String provider) { + if (provider == null) { + throw new IllegalArgumentException("provider==null"); + } + try { + return mService.isProviderEnabled(provider); + } catch (RemoteException ex) { + Log.e(TAG, "isProviderEnabled: RemoteException", ex); + return false; + } + } + + /** + * Returns a Location indicating the data from the last known + * location fix obtained from the given provider. 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 for the provider. + * @throws IllegalArgumentException if provider is null or doesn't exist + */ + public Location getLastKnownLocation(String provider) { + if (provider == null) { + throw new IllegalArgumentException("provider==null"); + } + try { + return mService.getLastKnownLocation(provider); + } catch (RemoteException ex) { + Log.e(TAG, "getLastKnowLocation: RemoteException", ex); + return null; + } + } + + // Mock provider support + + /** + * Creates a mock location provider and adds it to the set of active providers. + * + * @param name the provider name + * @param requiresNetwork + * @param requiresSatellite + * @param requiresCell + * @param hasMonetaryCost + * @param supportsAltitude + * @param supportsSpeed + * @param supportsBearing + * @param powerRequirement + * @param accuracy + * + * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present + * @throws IllegalArgumentException if a provider with the given name already exists + * + * {@hide} + */ + public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + try { + mService.addTestProvider(name, requiresNetwork, requiresSatellite, requiresCell, + hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing, powerRequirement, + accuracy); + } catch (RemoteException ex) { + Log.e(TAG, "addTestProvider: RemoteException", ex); + } + } + + /** + * Removes the mock location provider with the given name. + * + * @param provider the provider name + * + * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present + * @throws IllegalArgumentException if no provider with the given name exists + * + * {@hide} + */ + public void removeTestProvider(String provider) { + try { + mService.removeTestProvider(provider); + } catch (RemoteException ex) { + Log.e(TAG, "removeTestProvider: RemoteException", ex); + } + } + + /** + * Sets a mock location for the given provider. This location will be used in place + * of any actual location from the provider. + * + * @param provider the provider name + * @param loc the mock location + * + * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present + * @throws IllegalArgumentException if no provider with the given name exists + * + * {@hide} + */ + public void setTestProviderLocation(String provider, Location loc) { + try { + mService.setTestProviderLocation(provider, loc); + } catch (RemoteException ex) { + Log.e(TAG, "setTestProviderLocation: RemoteException", ex); + } + } + + /** + * Removes any mock location associated with the given provider. + * + * @param provider the provider name + * + * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present + * @throws IllegalArgumentException if no provider with the given name exists + * + * {@hide} + */ + public void clearTestProviderLocation(String provider) { + try { + mService.clearTestProviderLocation(provider); + } catch (RemoteException ex) { + Log.e(TAG, "clearTestProviderLocation: RemoteException", ex); + } + } + + /** + * 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 the ACCESS_MOCK_LOCATION permission is not present + * @throws IllegalArgumentException if no provider with the given name exists + * + * {@hide} + */ + public void setTestProviderEnabled(String provider, boolean enabled) { + try { + mService.setTestProviderEnabled(provider, enabled); + } catch (RemoteException ex) { + Log.e(TAG, "setTestProviderEnabled: RemoteException", ex); + } + } + + /** + * Removes any mock enabled value associated with the given provider. + * + * @param provider the provider name + * + * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present + * @throws IllegalArgumentException if no provider with the given name exists + * + * {@hide} + */ + public void clearTestProviderEnabled(String provider) { + try { + mService.clearTestProviderEnabled(provider); + } catch (RemoteException ex) { + Log.e(TAG, "clearTestProviderEnabled: RemoteException", ex); + } + + } + + /** + * 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 the ACCESS_MOCK_LOCATION permission is not present + * @throws IllegalArgumentException if no provider with the given name exists + * + * {@hide} + */ + public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) { + try { + mService.setTestProviderStatus(provider, status, extras, updateTime); + } catch (RemoteException ex) { + Log.e(TAG, "setTestProviderStatus: RemoteException", ex); + } + } + + /** + * Removes any mock status values associated with the given provider. + * + * @param provider the provider name + * + * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present + * @throws IllegalArgumentException if no provider with the given name exists + * + * {@hide} + */ + public void clearTestProviderStatus(String provider) { + try { + mService.clearTestProviderStatus(provider); + } catch (RemoteException ex) { + Log.e(TAG, "clearTestProviderStatus: RemoteException", ex); + } + } + + // GPS-specific support + + // This class is used to send GPS status events to the client's main thread. + private class GpsStatusListenerTransport extends IGpsStatusListener.Stub { + + private GpsStatusListener mListener; + private int mTTFF; + private int mSvCount; + private int[] mPrns; + private float[] mSnrs; + private float[] mElevations; + private float[] mAzimuths; + private int mEphemerisMask; + private int mAlmanacMask; + private int mUsedInFixMask; + + private static final int GPS_STARTED = 0; + private static final int GPS_STOPPED = 1; + private static final int GPS_FIRST_FIX = 2; + private static final int GPS_SV_STATUS = 3; + + GpsStatusListenerTransport(GpsStatusListener listener) { + mListener = listener; + } + + public void onGpsStarted() { + Message msg = Message.obtain(); + msg.what = GPS_STARTED; + mGpsHandler.sendMessage(msg); + } + + public void onGpsStopped() { + Message msg = Message.obtain(); + msg.what = GPS_STOPPED; + mGpsHandler.sendMessage(msg); + } + + public void onFirstFix(int ttff) { + mTTFF = ttff; + Message msg = Message.obtain(); + msg.what = GPS_FIRST_FIX; + mGpsHandler.sendMessage(msg); + } + + public void onSvStatusChanged(int svCount, int[] prns, float[] snrs, + float[] elevations, float[] azimuths, int ephemerisMask, + int almanacMask, int usedInFixMask) { + // synchronize here to ensure SV count matches data in the arrays + synchronized(this) { + mSvCount = svCount; + mPrns = prns; + mSnrs = snrs; + mElevations = elevations; + mAzimuths = azimuths; + mEphemerisMask = ephemerisMask; + mAlmanacMask = almanacMask; + mUsedInFixMask = usedInFixMask; + } + + Message msg = Message.obtain(); + msg.what = GPS_SV_STATUS; + // remove any SV status messages already in the queue + mGpsHandler.removeMessages(GPS_SV_STATUS); + mGpsHandler.sendMessage(msg); + } + + private final Handler mGpsHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case GPS_STARTED: + mListener.onGpsStarted(); + break; + case GPS_STOPPED: + mListener.onGpsStopped(); + break; + case GPS_FIRST_FIX: + mListener.onFirstFix(mTTFF); + break; + case GPS_SV_STATUS: + // synchronize here to ensure SV count matches data in the arrays + synchronized(this) { + mListener.onSvStatusChanged(mSvCount, mPrns, mSnrs, mElevations, + mAzimuths, mEphemerisMask, mAlmanacMask, mUsedInFixMask); + break; + } + } + } + }; + } + + /** + * Registers a GPS status listener. + * + * @param listener GPS status listener object to register. + * + * @return true if the listener was successfully registered. + * + * {@hide} + */ + public boolean registerGpsStatusListener(GpsStatusListener listener) { + boolean result; + + if (mGpsStatusListeners.get(listener) != null) { + // listener is already registered + return true; + } + try { + GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener); + result = mService.addGpsStatusListener(transport); + if (result) { + mGpsStatusListeners.put(listener, transport); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in registerGpsStatusListener: ", e); + result = false; + } + + return result; + } + + /** + * Unegisters a GPS status listener. + * + * @param listener GPS status listener object to unregister. + * + * {@hide} + */ + public void unregisterGpsStatusListener(GpsStatusListener listener) { + try { + GpsStatusListenerTransport transport = mGpsStatusListeners.remove(listener); + if (transport != null) { + mService.removeGpsStatusListener(transport); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in unregisterGpsStatusListener: ", e); + } + } + + /** + * 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. + * + * {@hide} + */ + public boolean sendExtraCommand(String provider, String command, Bundle extras) { + try { + return mService.sendExtraCommand(provider, command, extras); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in sendExtraCommand: ", e); + return false; + } + } +} diff --git a/location/java/android/location/LocationProvider.java b/location/java/android/location/LocationProvider.java new file mode 100644 index 0000000..b1670d5 --- /dev/null +++ b/location/java/android/location/LocationProvider.java @@ -0,0 +1,160 @@ +/* + * 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; + +/** + * 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 abstract class LocationProvider { + private static final String TAG = "LocationProvider"; + // A regular expression matching characters that may not appear + // in the name of a LocationProvider. + static final String BAD_CHARS_REGEX = "[^a-zA-Z0-9]"; + + private String mName; + + public static final int OUT_OF_SERVICE = 0; + public static final int TEMPORARILY_UNAVAILABLE = 1; + public static final int AVAILABLE = 2; + + /** + * 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 + */ + LocationProvider(String name) { + if (name.matches(BAD_CHARS_REGEX)) { + throw new IllegalArgumentException("name " + name + + " contains an illegal character"); + } + // Log.d(TAG, "Constructor: name = " + name); + mName = name; + } + + /** + * 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) { + if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && + (criteria.getAccuracy() < getAccuracy())) { + return false; + } + int criteriaPower = criteria.getPowerRequirement(); + if ((criteriaPower != Criteria.NO_REQUIREMENT) && + (criteriaPower < getPowerRequirement())) { + return false; + } + if (criteria.isAltitudeRequired() && !supportsAltitude()) { + return false; + } + if (criteria.isSpeedRequired() && !supportsSpeed()) { + return false; + } + if (criteria.isBearingRequired() && !supportsBearing()) { + return false; + } + return true; + } + + /** + * Returns true if the provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + public abstract boolean requiresNetwork(); + + /** + * Returns true if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + public abstract boolean requiresSatellite(); + + /** + * Returns true if the provider requires access to an appropriate + * cellular network (e.g., to make use of cell tower IDs), false + * otherwise. + */ + public abstract boolean requiresCell(); + + /** + * 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 abstract boolean hasMonetaryCost(); + + /** + * 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 abstract boolean supportsAltitude(); + + /** + * 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 abstract boolean supportsSpeed(); + + /** + * 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 abstract boolean supportsBearing(); + + /** + * Returns the power requirement for this provider. + * + * @return the power requirement for this provider, as one of the + * constants Criteria.POWER_REQUIREMENT_*. + */ + public abstract int getPowerRequirement(); + + /** + * 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 abstract int getAccuracy(); +} diff --git a/location/java/android/location/LocationProviderImpl.java b/location/java/android/location/LocationProviderImpl.java new file mode 100644 index 0000000..0962992 --- /dev/null +++ b/location/java/android/location/LocationProviderImpl.java @@ -0,0 +1,261 @@ +/* + * 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.CellState; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import android.os.Bundle; +import android.util.Config; +import android.util.Log; + +/** + * An abstract superclass for location provider implementations. + * Location provider implementations are typically instantiated by the + * location manager service in the system process, and location + * information is made available to implementations via the manager. + * + * {@hide} + */ +public abstract class LocationProviderImpl extends LocationProvider { + private static final String TAG = "LocationProviderImpl"; + + private static ArrayList<LocationProviderImpl> sProviders = + new ArrayList<LocationProviderImpl>(); + private static HashMap<String, LocationProviderImpl> sProvidersByName + = new HashMap<String, LocationProviderImpl>(); + + private boolean mLocationTracking = false; + private long mMinTime = 0; + + protected LocationProviderImpl(String name) { + super(name); + } + + public static void addProvider(LocationProviderImpl provider) { + sProviders.add(provider); + sProvidersByName.put(provider.getName(), provider); + } + + public static void removeProvider(LocationProviderImpl provider) { + sProviders.remove(provider); + sProvidersByName.remove(provider.getName()); + } + + public static List<LocationProviderImpl> getProviders() { + return new ArrayList<LocationProviderImpl>(sProviders); + } + + public static LocationProviderImpl getProvider(String name) { + return sProvidersByName.get(name); + } + + public static LocationProviderImpl loadFromClass(File classFile) { + if (!classFile.exists()) { + return null; + } + if (Config.LOGD) { + Log.d(TAG, "Loading class specifier file " + classFile.getPath()); + } + String className = null; + try { + BufferedReader br = + new BufferedReader(new FileReader(classFile), 8192); + className = br.readLine(); + br.close(); + Class providerClass = Class.forName(className); + if (Config.LOGD) { + Log.d(TAG, "Loading provider class " + providerClass.getName()); + } + LocationProviderImpl provider = + (LocationProviderImpl) providerClass.newInstance(); + if (Config.LOGD) { + Log.d(TAG, "Got provider instance " + provider); + } + + return provider; + } catch (IOException ioe) { + Log.e(TAG, "IOException loading config file " + + classFile.getPath(), ioe); + } catch (IllegalAccessException iae) { + Log.e(TAG, "IllegalAccessException loading class " + + className, iae); + } catch (InstantiationException ie) { + Log.e(TAG, "InstantiationException loading class " + + className, ie); + } catch (ClassNotFoundException cnfe) { + Log.e(TAG, "ClassNotFoundException loading class " + + className, cnfe); + } catch (ClassCastException cce) { + Log.e(TAG, "ClassCastException loading class " + + className, cce); + } + return null; + } + + /** + * Enables this provider. When enabled, calls to {@link #getStatus()} + * and {@link #getLocation} must be handled. Hardware may be started up + * when the provider is enabled. + */ + public abstract void enable(); + + /** + * Disables this provider. When disabled, calls to {@link #getStatus()} + * and {@link #getLocation} need not be handled. Hardware may be shut + * down while the provider is disabled. + */ + public abstract void disable(); + + /** + * Returns true if this provider is enabled, false otherwise; + */ + public abstract boolean isEnabled(); + + /** + * Returns a information on the status of this provider. + * {@link #OUT_OF_SERVICE} is returned if the provider is + * out of service, and this is not expected to change in the near + * future; {@link #TEMPORARILY_UNAVAILABLE} is returned if + * the provider is temporarily unavailable but is expected to be + * available shortly; and {@link #AVAILABLE} is returned + * if the provider is currently available. + */ + public int getStatus() { + return getStatus(null); + } + + /** + * Returns a information on the status of this provider. + * {@link #OUT_OF_SERVICE} is returned if the provider is + * out of service, and this is not expected to change in the near + * future; {@link #TEMPORARILY_UNAVAILABLE} is returned if + * the provider is temporarily unavailable but is expected to be + * available shortly; and {@link #AVAILABLE} is returned + * if the provider is currently available. + * + * <p> If extras is non-null, additional status information may be + * added to it in the form of provider-specific key/value pairs. + */ + public abstract int getStatus(Bundle extras); + + /** + * Returns the time at which the status was last updated. It is the + * responsibility of the provider to appropriately set this value + * using {@link android.os.SystemClock.elapsedRealtime()} each time + * there is a status update that it wishes to broadcast to all its + * listeners. The provider should be careful not to broadcast + * the same status again. + * + * @return time of last status update in millis since last reboot + */ + public long getStatusUpdateTime() { + return 0; + } + + /** + * Sets a Location object with the information gathered + * during the most recent fix. + * + * @param l location object to set + * @return true if a location fix is available + */ + public abstract boolean getLocation(Location l); + + /** + * Notifies the location provider that clients are listening for locations. + * Called with enable set to true when the first client is added and + * called with enable set to false when the last client is removed. + * This allows the provider to prepare for receiving locations, + * and to shut down when no clients are remaining. + * + * @param enable true if location tracking should be enabled. + */ + public void enableLocationTracking(boolean enable) { + mLocationTracking = enable; + } + + /** + * Returns true if the provider has any listeners + * + * @return true if provider is being tracked + */ + public boolean isLocationTracking() { + return mLocationTracking; + } + + /** + * Notifies the location provider of the smallest minimum time between updates amongst + * all clients that are listening for locations. This allows the provider to reduce + * the frequency of updates to match the requested frequency. + * + * @param minTime the smallest minTime value over all listeners for this provider. + */ + public void setMinTime(long minTime) { + mMinTime = minTime; + } + + /** + * Gets the smallest minimum time between updates amongst all the clients listening + * for locations. By default this value is 0 (as frqeuently as possible) + * + * @return the smallest minTime value over all listeners for this provider + */ + public long getMinTime() { + return mMinTime; + } + + /** + * Updates the network state for the given provider. This function must + * be overwritten if {@link #requiresNetwork} returns true. The state is + * {@link #TEMPORARILY_UNAVAILABLE} (disconnected), OR {@link #AVAILABLE} + * (connected or connecting). + * + * @param state data state + */ + public void updateNetworkState(int state) { + } + + /** + * Updates the cell state for the given provider. This function must be + * overwritten if {@link #requiresCell} returns true. + * + * @param state cell state + */ + public void updateCellState(CellState state) { + } + + /** + * Implements addditional location provider specific additional commands. + * + * @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 command, Bundle extras) { + return false; + } +} diff --git a/location/java/android/location/package.html b/location/java/android/location/package.html new file mode 100644 index 0000000..bbaeb42 --- /dev/null +++ b/location/java/android/location/package.html @@ -0,0 +1,12 @@ +<html> +<head> +<script type="text/javascript" src="http://www.corp.google.com/style/prettify.js"></script> +<script src="http://www.corp.google.com/eng/techpubs/include/navbar.js" type="text/javascript"></script> +</head> + +<body> + +<p>Classes defining Android location-based and related services.</p> + +</body> +</html> diff --git a/location/java/com/android/internal/location/CellState.java b/location/java/com/android/internal/location/CellState.java new file mode 100644 index 0000000..697fa92 --- /dev/null +++ b/location/java/com/android/internal/location/CellState.java @@ -0,0 +1,152 @@ +/* + * 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 com.android.internal.location; + +import android.telephony.CellLocation; +import android.telephony.ServiceState; +import android.telephony.gsm.GsmCellLocation; +import com.android.internal.telephony.TelephonyProperties; + +import android.os.SystemProperties; + +/** + * Stores the cell tower state + * + * {@hide} + */ +public class CellState { + + public static String TAG = "CellState"; + + public static int RADIO_TYPE_GPRS = 1; + public static int RADIO_TYPE_CDMA = 2; + public static int RADIO_TYPE_WCDMA = 3; + + private int mCid = -1; + private int mLac = -1; + private int mMcc = -1; + private int mMnc = -1; + private int mHomeMcc = -1; + private int mHomeMnc = -1; + private String mCarrier = null; + private int mRadioType = -1; + private long mTime = 0; + + public CellState() { + // constructor for invalid cell location + } + + public CellState(ServiceState service, CellLocation location) { + GsmCellLocation loc = (GsmCellLocation)location; + mLac = loc.getLac(); // example: 6032 + mCid = loc.getCid(); // example: 31792 + mTime = System.currentTimeMillis(); + + // Get radio type + String radioType = SystemProperties.get(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE); + if (radioType != null && (radioType.equals("GPRS") || radioType.equals("EDGE"))) { + mRadioType = RADIO_TYPE_GPRS; + } else if (radioType != null && radioType.equals("UMTS")) { + mRadioType = RADIO_TYPE_WCDMA; + } + + // Get MCC/MNC + String operator = service.getOperatorNumeric(); + if (operator != null && !operator.equals("")) { + String mcc = operator.substring(0, 3); + String mnc = operator.substring(3); + + mMcc = Integer.parseInt(mcc); + mMnc = Integer.parseInt(mnc); + } + + // Get Home MCC/MNC + String homeOperator = SystemProperties.get(TelephonyProperties.PROPERTY_SIM_OPERATOR_NUMERIC); + if (homeOperator != null && !homeOperator.equals("")) { + String mcc = homeOperator.substring(0, 3); + String mnc = homeOperator.substring(3); + + mHomeMcc = Integer.parseInt(mcc); + mHomeMnc = Integer.parseInt(mnc); + } + + // Get Carrier + String carrier = service.getOperatorAlphaLong(); + if (carrier != null && !carrier.equals("")) { + mCarrier = carrier; + } + + //Log.d(TAG, mLac +"," + mCid + "," + mMnc +"," + mMcc + "," + mHomeMcc + "," + + // mHomeMnc + "," + mCarrier + "," + mRadioType); + } + + public int getCid() { + return mCid; + } + + public int getLac() { + return mLac; + } + + public int getMcc() { + return mMcc; + } + + public int getMnc() { + return mMnc; + } + + public int getHomeMcc() { + return mHomeMcc; + } + + public void setHomeMcc(int homeMcc) { + this.mHomeMcc = homeMcc; + } + + public int getHomeMnc() { + return mHomeMnc; + } + + public void setHomeMnc(int homeMnc) { + this.mHomeMnc = homeMnc; + } + + public String getCarrier() { + return mCarrier; + } + + public void setCarrier(String carrier) { + this.mCarrier = carrier; + } + + public int getRadioType() { + return mRadioType; + } + + public long getTime() { + return mTime; + } + + public boolean equals(CellState other) { + return (mCid == other.mCid && mLac == other.mLac); + } + + public boolean isValid() { + return (mCid != -1 && mLac != -1); + } +} diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java new file mode 100644 index 0000000..d0e4f49 --- /dev/null +++ b/location/java/com/android/internal/location/GpsLocationProvider.java @@ -0,0 +1,879 @@ +/* + * 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 com.android.internal.location; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Properties; + +import android.content.Context; +import android.content.Intent; +import android.location.Criteria; +import android.location.IGpsStatusListener; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.location.LocationProviderImpl; +import android.net.SntpClient; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Config; +import android.util.Log; + +/** + * A GPS implementation of LocationProvider used by LocationManager. + * + * {@hide} + */ +public class GpsLocationProvider extends LocationProviderImpl { + + private static final String TAG = "GpsLocationProvider"; + + /** + * 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_ENABLED + * + * {@hide} + */ + public static final String GPS_ENABLED_CHANGE_ACTION = + "android.location.GPS_ENABLED_CHANGE"; + + /** + * 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_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_ENABLED = "enabled"; + + // these need to match GpsStatusValue defines in gps.h + private static final int GPS_STATUS_NONE = 0; + private static final int GPS_STATUS_SESSION_BEGIN = 1; + private static final int GPS_STATUS_SESSION_END = 2; + private static final int GPS_STATUS_ENGINE_ON = 3; + private static final int GPS_STATUS_ENGINE_OFF = 4; + + // these need to match GpsLocationFlags enum in gps.h + private static final int LOCATION_INVALID = 0; + private static final int LOCATION_HAS_LAT_LONG = 1; + private static final int LOCATION_HAS_ALTITUDE = 2; + private static final int LOCATION_HAS_SPEED = 4; + private static final int LOCATION_HAS_BEARING = 8; + private static final int LOCATION_HAS_ACCURACY = 16; + +// IMPORTANT - the GPS_DELETE_* symbols here must match constants in GpsLocationProvider.java + private static final int GPS_DELETE_EPHEMERIS = 0x0001; + private static final int GPS_DELETE_ALMANAC = 0x0002; + private static final int GPS_DELETE_POSITION = 0x0004; + private static final int GPS_DELETE_TIME = 0x0008; + private static final int GPS_DELETE_IONO = 0x0010; + private static final int GPS_DELETE_UTC = 0x0020; + private static final int GPS_DELETE_HEALTH = 0x0040; + private static final int GPS_DELETE_SVDIR = 0x0080; + private static final int GPS_DELETE_SVSTEER = 0x0100; + private static final int GPS_DELETE_SADATA = 0x0200; + private static final int GPS_DELETE_RTI = 0x0400; + private static final int GPS_DELETE_CELLDB_INFO = 0x8000; + private static final int GPS_DELETE_ALL = 0xFFFF; + + private static final String PROPERTIES_FILE = "/etc/gps.conf"; + + private int mLocationFlags = LOCATION_INVALID; + + // current status + private int mStatus = TEMPORARILY_UNAVAILABLE; + + // time for last status update + private long mStatusUpdateTime = SystemClock.elapsedRealtime(); + + // turn off GPS fix icon if we haven't received a fix in 10 seconds + private static final long RECENT_FIX_TIMEOUT = 10 * 1000; + + // true if we are enabled + private boolean mEnabled; + // true if we are enabled for location updates + private boolean mLocationTracking; + + // true if we have network connectivity + private boolean mNetworkAvailable; + + // true if GPS is navigating + private boolean mNavigating; + + // requested frequency of fixes, in seconds + private int mFixInterval = 1; + + // true if we started navigation + private boolean mStarted; + + // for calculating time to first fix + private long mFixRequestTime = 0; + // time to first fix for most recent session + private int mTTFF = 0; + // time we received our last fix + private long mLastFixTime; + + // properties loaded from PROPERTIES_FILE + private Properties mProperties; + + private Context mContext; + private Location mLocation = new Location(LocationManager.GPS_PROVIDER); + private Bundle mLocationExtras = new Bundle(); + private ArrayList<Listener> mListeners = new ArrayList<Listener>(); + private GpsEventThread mEventThread; + private GpsNetworkThread mNetworkThread; + + // how often to request NTP time, in milliseconds + // current setting 4 hours + private static final long NTP_INTERVAL = 4*60*60*1000; + // how long to wait if we have a network error in NTP or XTRA downloading + // current setting - 5 minutes + private static final long RETRY_INTERVAL = 5*60*1000; + + private LocationCollector mCollector; + + public static boolean isSupported() { + return native_is_supported(); + } + + public GpsLocationProvider(Context context, LocationCollector collector) { + super(LocationManager.GPS_PROVIDER); + mContext = context; + mCollector = collector; + + mProperties = new Properties(); + try { + File file = new File(PROPERTIES_FILE); + FileInputStream stream = new FileInputStream(file); + mProperties.load(stream); + stream.close(); + } catch (IOException e) { + Log.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE, e); + } + } + + /** + * Returns true if the provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + @Override + public boolean requiresNetwork() { + // We want updateNetworkState() to get called when the network state changes + // for XTRA and NTP time injection support. + return true; + } + + public void updateNetworkState(int state) { + mNetworkAvailable = (state == LocationProvider.AVAILABLE); + + if (Config.LOGD) { + Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable")); + } + + if (mNetworkAvailable && mNetworkThread != null && mEnabled) { + // signal the network thread when the network becomes available + mNetworkThread.signal(); + } + } + + /** + * Returns true if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + @Override + public boolean requiresSatellite() { + return true; + } + + /** + * Returns true if the provider requires access to an appropriate + * cellular network (e.g., to make use of cell tower IDs), false + * otherwise. + */ + @Override + public boolean requiresCell() { + return false; + } + + /** + * 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. + */ + @Override + public boolean hasMonetaryCost() { + return false; + } + + /** + * 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. + */ + @Override + public boolean supportsAltitude() { + return true; + } + + /** + * 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. + */ + @Override + public boolean supportsSpeed() { + return true; + } + + /** + * 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. + */ + @Override + public boolean supportsBearing() { + return true; + } + + /** + * Returns the power requirement for this provider. + * + * @return the power requirement for this provider, as one of the + * constants Criteria.POWER_REQUIREMENT_*. + */ + @Override + public int getPowerRequirement() { + return Criteria.POWER_HIGH; + } + + /** + * Returns the horizontal accuracy of this provider + * + * @return the accuracy of location from this provider, as one + * of the constants Criteria.ACCURACY_*. + */ + @Override + public int getAccuracy() { + return Criteria.ACCURACY_FINE; + } + + /** + * Enables this provider. When enabled, calls to getStatus() + * and getLocation() must be handled. Hardware may be started up + * when the provider is enabled. + */ + @Override + public void enable() { + if (Config.LOGD) Log.d(TAG, "enable"); + mEnabled = native_init(); + + if (mEnabled) { + // run event listener thread while we are enabled + mEventThread = new GpsEventThread(); + mEventThread.start(); + + // run network thread for NTP and XTRA support + if (mNetworkThread == null) { + mNetworkThread = new GpsNetworkThread(); + mNetworkThread.start(); + } else { + mNetworkThread.signal(); + } + } else { + Log.w(TAG, "Failed to enable location provider"); + } + } + + /** + * Disables this provider. When disabled, calls to getStatus() + * and getLocation() need not be handled. Hardware may be shut + * down while the provider is disabled. + */ + @Override + public void disable() { + if (Config.LOGD) Log.d(TAG, "disable"); + mEnabled = false; + stopNavigating(); + native_disable(); + + // make sure our event thread exits + if (mEventThread != null) { + try { + mEventThread.join(); + } catch (InterruptedException e) { + Log.w(TAG, "InterruptedException when joining mEventThread"); + } + mEventThread = null; + } + + native_cleanup(); + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public int getStatus(Bundle extras) { + if (extras != null) { + extras.putInt("satellites", mSvCount); + } + return mStatus; + } + + private void updateStatus(int status, int svCount) { + if (status != mStatus || svCount != mSvCount) { + mStatus = status; + mSvCount = svCount; + mLocationExtras.putInt("satellites", svCount); + mStatusUpdateTime = SystemClock.elapsedRealtime(); + } + } + + @Override + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + @Override + public boolean getLocation(Location l) { + synchronized (mLocation) { + // don't report locations without latitude and longitude + if ((mLocationFlags & LOCATION_HAS_LAT_LONG) == 0) { + return false; + } + l.set(mLocation); + l.setExtras(mLocationExtras); + return true; + } + } + + @Override + public void enableLocationTracking(boolean enable) { + if (mLocationTracking == enable) { + return; + } + + if (enable) { + mFixRequestTime = System.currentTimeMillis(); + mTTFF = 0; + mLastFixTime = 0; + startNavigating(); + } else { + stopNavigating(); + } + mLocationTracking = enable; + } + + @Override + public boolean isLocationTracking() { + return mLocationTracking; + } + + @Override + public void setMinTime(long minTime) { + super.setMinTime(minTime); + if (Config.LOGD) Log.d(TAG, "setMinTime " + minTime); + + if (minTime >= 0) { + int interval = (int)(minTime/1000); + if (interval < 1) { + interval = 1; + } + mFixInterval = interval; + native_set_fix_frequency(mFixInterval); + } + } + + private final class Listener implements IBinder.DeathRecipient { + final IGpsStatusListener mListener; + + int mSensors = 0; + + Listener(IGpsStatusListener listener) { + mListener = listener; + } + + public void binderDied() { + if (Config.LOGD) Log.d(TAG, "GPS status listener died"); + + synchronized(mListeners) { + mListeners.remove(this); + } + } + } + + public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException { + if (listener == null) throw new NullPointerException("listener is null in addGpsStatusListener"); + + synchronized(mListeners) { + IBinder binder = listener.asBinder(); + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener test = mListeners.get(i); + if (binder.equals(test.mListener.asBinder())) { + // listener already added + return; + } + } + + Listener l = new Listener(listener); + binder.linkToDeath(l, 0); + mListeners.add(l); + } + } + + public void removeGpsStatusListener(IGpsStatusListener listener) { + if (listener == null) throw new NullPointerException("listener is null in addGpsStatusListener"); + + synchronized(mListeners) { + IBinder binder = listener.asBinder(); + Listener l = null; + int size = mListeners.size(); + for (int i = 0; i < size && l == null; i++) { + Listener test = mListeners.get(i); + if (binder.equals(test.mListener.asBinder())) { + // listener already added + return; + } + } + + if (l != null) { + mListeners.remove(l); + binder.unlinkToDeath(l, 0); + } + } + } + + @Override + public boolean sendExtraCommand(String command, Bundle extras) { + + if ("delete_aiding_data".equals(command)) { + return deleteAidingData(extras); + } + + Log.w(TAG, "sendExtraCommand: unknown command " + command); + return false; + } + + private boolean deleteAidingData(Bundle extras) { + int flags; + + if (extras == null) { + flags = GPS_DELETE_ALL; + } else { + flags = 0; + if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS; + if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC; + if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION; + if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME; + if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO; + if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC; + if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH; + if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR; + if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER; + if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA; + if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI; + if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO; + if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL; + } + + if (flags != 0) { + native_delete_aiding_data(flags); + return true; + } + + return false; + } + + public void startNavigating() { + if (!mStarted) { + if (Config.LOGV) Log.v(TAG, "startNavigating"); + mStarted = true; + if (!native_start(false, mFixInterval)) { + mStarted = false; + Log.e(TAG, "native_start failed in startNavigating()"); + } + + // reset SV count to zero + updateStatus(TEMPORARILY_UNAVAILABLE, 0); + } + } + + public void stopNavigating() { + if (Config.LOGV) Log.v(TAG, "stopNavigating"); + if (mStarted) { + mStarted = false; + native_stop(); + mTTFF = 0; + mLastFixTime = 0; + mLocationFlags = LOCATION_INVALID; + + // reset SV count to zero + updateStatus(TEMPORARILY_UNAVAILABLE, 0); + } + } + + /** + * called from native code to update our position. + */ + private void reportLocation(int flags, double latitude, double longitude, double altitude, + float speed, float bearing, float accuracy, long timestamp) { + if (Config.LOGV) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude + + " timestamp: " + timestamp); + + mLastFixTime = System.currentTimeMillis(); + // report time to first fix + if (mTTFF == 0) { + mTTFF = (int)(mLastFixTime - mFixRequestTime); + if (Config.LOGD) Log.d(TAG, "TTFF: " + mTTFF); + + // notify status listeners + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onFirstFix(mTTFF); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in stopNavigating"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + } + + synchronized (mLocation) { + mLocationFlags = flags; + if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mLocation.setLatitude(latitude); + mLocation.setLongitude(longitude); + mLocation.setTime(timestamp); + } + if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) { + mLocation.setAltitude(altitude); + } else { + mLocation.removeAltitude(); + } + if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { + mLocation.setSpeed(speed); + } else { + mLocation.removeSpeed(); + } + if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) { + mLocation.setBearing(bearing); + } else { + mLocation.removeBearing(); + } + if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) { + mLocation.setAccuracy(accuracy); + } else { + mLocation.removeAccuracy(); + } + + // Send to collector + if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mCollector.updateLocation(mLocation); + } + } + + if (mStarted && mStatus != AVAILABLE) { + // send an intent to notify that the GPS is receiving fixes. + Intent intent = new Intent(GPS_FIX_CHANGE_ACTION); + intent.putExtra(EXTRA_ENABLED, true); + mContext.sendBroadcast(intent); + updateStatus(AVAILABLE, mSvCount); + } + } + + /** + * called from native code to update our status + */ + private void reportStatus(int status) { + if (Config.LOGV) Log.v(TAG, "reportStatus status: " + status); + + boolean wasNavigating = mNavigating; + mNavigating = (status == GPS_STATUS_SESSION_BEGIN || status == GPS_STATUS_ENGINE_ON); + + if (wasNavigating != mNavigating) { + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + if (mNavigating) { + listener.mListener.onGpsStarted(); + } else { + listener.mListener.onGpsStopped(); + } + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportStatus"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + + // send an intent to notify that the GPS has been enabled or disabled. + Intent intent = new Intent(GPS_ENABLED_CHANGE_ACTION); + intent.putExtra(EXTRA_ENABLED, mNavigating); + mContext.sendBroadcast(intent); + } + } + + /** + * called from native code to update SV info + */ + private void reportSvStatus() { + + int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks); + + synchronized(mListeners) { + int size = mListeners.size(); + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, + mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], + mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportSvInfo"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + + if (Config.LOGD) { + if (Config.LOGV) Log.v(TAG, "SV count: " + svCount + + " ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) + + " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK])); + for (int i = 0; i < svCount; i++) { + if (Config.LOGV) Log.v(TAG, "sv: " + mSvs[i] + + " snr: " + (float)mSnrs[i]/10 + + " elev: " + mSvElevations[i] + + " azimuth: " + mSvAzimuths[i] + + ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " E") + + ((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " A") + + ((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U")); + } + } + + updateStatus(mStatus, svCount); + + if (mNavigating && mStatus == AVAILABLE && mLastFixTime > 0 && + System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT) { + // send an intent to notify that the GPS is no longer receiving fixes. + Intent intent = new Intent(GPS_FIX_CHANGE_ACTION); + intent.putExtra(EXTRA_ENABLED, false); + mContext.sendBroadcast(intent); + updateStatus(TEMPORARILY_UNAVAILABLE, mSvCount); + } + } + + private void xtraDownloadRequest() { + if (Config.LOGD) Log.d(TAG, "xtraDownloadRequest"); + if (mNetworkThread != null) { + mNetworkThread.xtraDownloadRequest(); + } + } + + private class GpsEventThread extends Thread { + + public GpsEventThread() { + super("GpsEventThread"); + } + + public void run() { + if (Config.LOGD) Log.d(TAG, "GpsEventThread starting"); + // thread exits after disable() is called and navigation has stopped + while (mEnabled || mNavigating) { + // this will wait for an event from the GPS, + // which will be reported via reportLocation or reportStatus + native_wait_for_event(); + } + if (Config.LOGD) Log.d(TAG, "GpsEventThread exiting"); + } + } + + private class GpsNetworkThread extends Thread { + + private long mNextNtpTime = 0; + private long mNextXtraTime = 0; + private boolean mXtraDownloadRequested = false; + + public GpsNetworkThread() { + super("GpsNetworkThread"); + } + + public void run() { + if (Config.LOGD) Log.d(TAG, "NetworkThread starting"); + + SntpClient client = new SntpClient(); + GpsXtraDownloader xtraDownloader = null; + + if (native_supports_xtra()) { + xtraDownloader = new GpsXtraDownloader(mContext, mProperties); + } + + // thread exits after disable() is called + while (mEnabled) { + long waitTime = getWaitTime(); + do { + synchronized (this) { + try { + if (!mNetworkAvailable) { + if (Config.LOGD) Log.d(TAG, "NetworkThread wait for network"); + wait(); + } else if (waitTime > 0) { + if (Config.LOGD) Log.d(TAG, "NetworkThread wait for " + waitTime + "ms"); + wait(waitTime); + } + } catch (InterruptedException e) { + if (Config.LOGD) Log.d(TAG, "InterruptedException in GpsNetworkThread"); + } + } + waitTime = getWaitTime(); + } while (mEnabled && ((!mXtraDownloadRequested && waitTime > 0) || !mNetworkAvailable)); + if (Config.LOGD) Log.d(TAG, "NetworkThread out of wake loop"); + + if (mEnabled) { + if (mNextNtpTime <= System.currentTimeMillis()) { + String ntpServer = mProperties.getProperty("NTP_SERVER", "pool.ntp.org"); + if (Config.LOGD) Log.d(TAG, "Requesting time from NTP server " + ntpServer); + if (client.requestTime(ntpServer, 10000)) { + long time = client.getNtpTime(); + long timeReference = client.getNtpTimeReference(); + int certainty = (int)(client.getRoundTripTime()/2); + + if (Config.LOGD) Log.d(TAG, "calling native_inject_time: " + + time + " reference: " + timeReference + + " certainty: " + certainty); + + native_inject_time(time, timeReference, certainty); + mNextNtpTime = System.currentTimeMillis() + NTP_INTERVAL; + } else { + if (Config.LOGD) Log.d(TAG, "requestTime failed"); + mNextNtpTime = System.currentTimeMillis() + RETRY_INTERVAL; + } + } + + if ((mXtraDownloadRequested || + (mNextXtraTime > 0 && mNextXtraTime <= System.currentTimeMillis())) && + xtraDownloader != null) { + byte[] data = xtraDownloader.downloadXtraData(); + if (data != null) { + if (Config.LOGD) Log.d(TAG, "calling native_inject_xtra_data"); + native_inject_xtra_data(data, data.length); + mNextXtraTime = 0; + mXtraDownloadRequested = false; + } else { + mNextXtraTime = System.currentTimeMillis() + RETRY_INTERVAL; + } + } + } + } + if (Config.LOGD) Log.d(TAG, "NetworkThread exiting"); + } + + synchronized void xtraDownloadRequest() { + mXtraDownloadRequested = true; + notify(); + } + + synchronized void signal() { + notify(); + } + + private long getWaitTime() { + long now = System.currentTimeMillis(); + long waitTime = mNextNtpTime - now; + if (mNextXtraTime != 0) { + long xtraWaitTime = mNextXtraTime - now; + if (xtraWaitTime < waitTime) { + waitTime = xtraWaitTime; + } + } + if (waitTime < 0) { + waitTime = 0; + } + return waitTime; + } + } + + // for GPS SV statistics + private static final int MAX_SVS = 32; + private static final int EPHEMERIS_MASK = 0; + private static final int ALMANAC_MASK = 1; + private static final int USED_FOR_FIX_MASK = 2; + + // preallocated arrays, to avoid memory allocation in reportStatus() + private int mSvs[] = new int[MAX_SVS]; + private float mSnrs[] = new float[MAX_SVS]; + private float mSvElevations[] = new float[MAX_SVS]; + private float mSvAzimuths[] = new float[MAX_SVS]; + private int mSvMasks[] = new int[3]; + private int mSvCount; + + static { class_init_native(); } + private static native void class_init_native(); + private static native boolean native_is_supported(); + + private native boolean native_init(); + private native void native_disable(); + private native void native_cleanup(); + private native boolean native_start(boolean singleFix, int fixInterval); + private native boolean native_stop(); + private native void native_set_fix_frequency(int fixFrequency); + private native void native_delete_aiding_data(int flags); + private native void native_wait_for_event(); + // returns number of SVs + // mask[0] is ephemeris mask and mask[1] is almanac mask + private native int native_read_sv_status(int[] svs, float[] snrs, + float[] elevations, float[] azimuths, int[] masks); + + private native void native_inject_time(long time, long timeReference, int uncertainty); + private native boolean native_supports_xtra(); + private native void native_inject_xtra_data(byte[] data, int length); +} diff --git a/location/java/com/android/internal/location/GpsXtraDownloader.java b/location/java/com/android/internal/location/GpsXtraDownloader.java new file mode 100644 index 0000000..6efbbf6 --- /dev/null +++ b/location/java/com/android/internal/location/GpsXtraDownloader.java @@ -0,0 +1,163 @@ +/* + * 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 com.android.internal.location; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.params.ConnRouteParams; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Properties; + +import android.content.Context; +import android.net.Proxy; +import android.net.http.AndroidHttpClient; +import android.util.Log; + +/** + * A class for downloading GPS XTRA data. + * + * {@hide} + */ +public class GpsXtraDownloader { + + private static final String TAG = "GpsXtraDownloader"; + + private Context mContext; + private String[] mXtraServers; + // to load balance our server requests + private int mNextServerIndex = 0; + + GpsXtraDownloader(Context context, Properties properties) { + mContext = context; + + // read XTRA servers from the Properties object + int count = 0; + String server1 = properties.getProperty("XTRA_SERVER_1"); + String server2 = properties.getProperty("XTRA_SERVER_2"); + String server3 = properties.getProperty("XTRA_SERVER_3"); + if (server1 != null) count++; + if (server2 != null) count++; + if (server3 != null) count++; + + if (count == 0) { + Log.e(TAG, "No XTRA servers were specified in the GPS configuration"); + } else { + mXtraServers = new String[count]; + count = 0; + if (server1 != null) mXtraServers[count++] = server1; + if (server2 != null) mXtraServers[count++] = server2; + if (server3 != null) mXtraServers[count++] = server3; + } + } + + byte[] downloadXtraData() { + String proxyHost = Proxy.getHost(mContext); + int proxyPort = Proxy.getPort(mContext); + boolean useProxy = (proxyHost != null && proxyPort != -1); + byte[] result = null; + int startIndex = mNextServerIndex; + + if (mXtraServers == null) { + return null; + } + + // load balance our requests among the available servers + while (result == null) { + result = doDownload(mXtraServers[mNextServerIndex], useProxy, proxyHost, proxyPort); + + // increment mNextServerIndex and wrap around if necessary + mNextServerIndex++; + if (mNextServerIndex == mXtraServers.length) { + mNextServerIndex = 0; + } + // break if we have tried all the servers + if (mNextServerIndex == startIndex) break; + } + + return result; + } + + protected static byte[] doDownload(String url, boolean isProxySet, + String proxyHost, int proxyPort) { + AndroidHttpClient client = null; + try { + client = AndroidHttpClient.newInstance("Android"); + HttpUriRequest req = new HttpGet(url); + + if (isProxySet) { + HttpHost proxy = new HttpHost(proxyHost, proxyPort); + ConnRouteParams.setDefaultProxy(req.getParams(), proxy); + } + + req.addHeader( + "Accept", + "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"); + + req.addHeader( + "x-wap-profile", + "http://www.openmobilealliance.org/tech/profiles/UAPROF/ccppschema-20021212#"); + + HttpResponse response = client.execute(req); + StatusLine status = response.getStatusLine(); + if (status.getStatusCode() != 200) { // HTTP 200 is success. + Log.d(TAG, "HTTP error: " + status.getReasonPhrase()); + return null; + } + + HttpEntity entity = response.getEntity(); + byte[] body = null; + if (entity != null) { + try { + if (entity.getContentLength() > 0) { + body = new byte[(int) entity.getContentLength()]; + DataInputStream dis = new DataInputStream(entity.getContent()); + try { + dis.readFully(body); + } finally { + try { + dis.close(); + } catch (IOException e) { + Log.e(TAG, "Unexpected IOException.", e); + } + } + } + } finally { + if (entity != null) { + entity.consumeContent(); + } + } + } + return body; + } catch (Exception e) { + Log.d(TAG, "error " + e); + } finally { + if (client != null) { + client.close(); + } + } + return null; + } + +} + diff --git a/location/java/com/android/internal/location/LocationCache.java b/location/java/com/android/internal/location/LocationCache.java new file mode 100644 index 0000000..f0928f9 --- /dev/null +++ b/location/java/com/android/internal/location/LocationCache.java @@ -0,0 +1,577 @@ +// Copyright 2007 The Android Open Source Project + +package com.android.internal.location; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import android.location.Location; +import android.location.LocationManager; +import android.net.wifi.ScanResult; +import android.os.Bundle; +import android.util.Log; + +/** + * Data store to cache cell-id and wifi locations from the network + * + * {@hide} + */ +public class LocationCache { + private static final String TAG = "LocationCache"; + + // Version of cell cache + private static final int CACHE_DB_VERSION = 1; + + // Don't save cache more than once every minute + private static final long SAVE_FREQUENCY = 60 * 1000; + + // Location of the cache file; + private static final String mCellCacheFile = "cache.cell"; + private static final String mWifiCacheFile = "cache.wifi"; + + // Maximum time (in millis) that a record is valid for, before it needs + // to be refreshed from the server. + private static final long MAX_CELL_REFRESH_RECORD_AGE = 12 * 60 * 60 * 1000; // 12 hours + private static final long MAX_WIFI_REFRESH_RECORD_AGE = 48 * 60 * 60 * 1000; // 48 hours + + // Cache sizes + private static final int MAX_CELL_RECORDS = 50; + private static final int MAX_WIFI_RECORDS = 200; + + // Cache constants + private static final long CELL_SMOOTHING_WINDOW = 30 * 1000; // 30 seconds + private static final int WIFI_MIN_AP_REQUIRED = 2; + private static final int WIFI_MAX_MISS_ALLOWED = 5; + private static final int MAX_ACCURACY_ALLOWED = 5000; // 5km + + // Caches + private final Cache<Record> mCellCache; + private final Cache<Record> mWifiCache; + + // Currently calculated centroids + private final LocationCentroid mCellCentroid = new LocationCentroid(); + private final LocationCentroid mWifiCentroid = new LocationCentroid(); + + // Extra key and values + private final String EXTRA_KEY_LOCATION_TYPE = "networkLocationType"; + private final String EXTRA_VALUE_LOCATION_TYPE_CELL = "cell"; + private final String EXTRA_VALUE_LOCATION_TYPE_WIFI = "wifi"; + + public LocationCache() { + mCellCache = new Cache<Record>(LocationManager.SYSTEM_DIR, mCellCacheFile, + MAX_CELL_RECORDS, MAX_CELL_REFRESH_RECORD_AGE); + mWifiCache = new Cache<Record>(LocationManager.SYSTEM_DIR, mWifiCacheFile, + MAX_WIFI_RECORDS, MAX_WIFI_REFRESH_RECORD_AGE); + } + + /** + * Looks up network location on device cache + * + * @param cellState primary cell state + * @param cellHistory history of cell states + * @param scanResults wifi scan results + * @param result location object to fill if location is found + * @return true if cache was able to answer query (successfully or not), false if call to + * server is required + */ + public synchronized boolean lookup(CellState cellState, List<CellState> cellHistory, + List<ScanResult> scanResults, Location result) { + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "including cell:" + (cellState != null) + + ", wifi:" + ((scanResults != null)? scanResults.size() : "null")); + } + + long now = System.currentTimeMillis(); + + mCellCentroid.reset(); + mWifiCentroid.reset(); + + if (cellState != null) { + String primaryCellKey = getCellCacheKey(cellState.getMcc(), cellState.getMnc(), + cellState.getLac(), cellState.getCid()); + Record record = mCellCache.lookup(primaryCellKey); + + if (record == null) { + // Make a server request if primary cell doesn't exist in DB + return false; + } + + if (record.isValid()) { + mCellCentroid.addLocation(record.getLat(), record.getLng(), record.getAccuracy(), + record.getConfidence()); + } + } + + if (cellHistory != null) { + for (CellState historicalCell : cellHistory) { + // Cell location might need to be smoothed if you are on the border of two cells + if (now - historicalCell.getTime() < CELL_SMOOTHING_WINDOW) { + String historicalCellKey = getCellCacheKey(historicalCell.getMcc(), + historicalCell.getMnc(), historicalCell.getLac(), historicalCell.getCid()); + Record record = mCellCache.lookup(historicalCellKey); + if (record != null && record.isValid()) { + mCellCentroid.addLocation(record.getLat(), record.getLng(), + record.getAccuracy(), record.getConfidence()); + } + } + } + } + + if (scanResults != null) { + int miss = 0; + for (ScanResult scanResult : scanResults) { + String wifiKey = scanResult.BSSID; + Record record = mWifiCache.lookup(wifiKey); + if (record == null) { + miss++; + } else { + if (record.isValid()) { + mWifiCentroid.addLocation(record.getLat(), record.getLng(), + record.getAccuracy(), record.getConfidence()); + } + } + } + + if (mWifiCentroid.getNumber() >= WIFI_MIN_AP_REQUIRED) { + // Try to return best out of the available cell or wifi location + } else if (miss > Math.min(WIFI_MAX_MISS_ALLOWED, (scanResults.size()+1)/2)) { + // Make a server request + return false; + } else { + // Don't use wifi location, only consider using cell location + mWifiCache.save(); + mWifiCentroid.reset(); + } + } + + if (mCellCentroid.getNumber() > 0) { + mCellCache.save(); + } + if (mWifiCentroid.getNumber() > 0) { + mWifiCache.save(); + } + + int cellAccuracy = mCellCentroid.getAccuracy(); + int wifiAccuracy = mWifiCentroid.getAccuracy(); + + int cellConfidence = mCellCentroid.getConfidence(); + int wifiConfidence = mWifiCentroid.getConfidence(); + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "cellAccuracy:" + cellAccuracy+ ", wifiAccuracy:" + wifiAccuracy); + } + + if (mCellCentroid.getNumber() != 0 && cellAccuracy <= MAX_ACCURACY_ALLOWED && + (mWifiCentroid.getNumber() == 0 || cellConfidence >= wifiConfidence || + cellAccuracy < wifiAccuracy)) { + // Use cell results + result.setAccuracy(cellAccuracy); + result.setLatitude(mCellCentroid.getCentroidLat()); + result.setLongitude(mCellCentroid.getCentroidLng()); + result.setTime(now); + + Bundle extras = result.getExtras() == null ? new Bundle() : result.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_TYPE, EXTRA_VALUE_LOCATION_TYPE_CELL); + result.setExtras(extras); + + } else if (mWifiCentroid.getNumber() != 0 && wifiAccuracy <= MAX_ACCURACY_ALLOWED) { + // Use wifi results + result.setAccuracy(wifiAccuracy); + result.setLatitude(mWifiCentroid.getCentroidLat()); + result.setLongitude(mWifiCentroid.getCentroidLng()); + result.setTime(now); + + Bundle extras = result.getExtras() == null ? new Bundle() : result.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_TYPE, EXTRA_VALUE_LOCATION_TYPE_WIFI); + result.setExtras(extras); + + } else { + // Return invalid location + result.setAccuracy(-1); + } + + // don't make a server request + return true; + } + + public synchronized void insert(int mcc, int mnc, int lac, int cid, double lat, double lng, + int accuracy, int confidence, long time) { + String key = getCellCacheKey(mcc, mnc, lac, cid); + if (accuracy <= 0) { + mCellCache.insert(key, new Record()); + } else { + mCellCache.insert(key, new Record(accuracy, confidence, lat, lng, time)); + } + } + + public synchronized void insert(String bssid, double lat, double lng, int accuracy, + int confidence, long time) { + if (accuracy <= 0) { + mWifiCache.insert(bssid, new Record()); + } else { + mWifiCache.insert(bssid, new Record(accuracy, confidence, lat, lng, time)); + } + } + + public synchronized void save() { + mCellCache.save(); + mWifiCache.save(); + } + + /** + * Cell or Wifi location record + */ + public static class Record { + + private final double lat; + private final double lng; + private final int accuracy; + private final int confidence; + + // Time (since the epoch) of original reading. + private final long originTime; + + public static Record read(DataInput dataInput) throws IOException { + final int accuracy = dataInput.readInt(); + final int confidence = dataInput.readInt(); + final double lat = dataInput.readDouble(); + final double lng = dataInput.readDouble(); + final long readingTime = dataInput.readLong(); + return new Record(accuracy, confidence, lat, lng, readingTime); + } + + /** + * Creates an "invalid" record indicating there was no location data + * available for the given data + */ + public Record() { + this(-1, 0, 0, 0, System.currentTimeMillis()); + } + + /** + * Creates a Record + * + * @param accuracy acuracy in meters. If < 0, then this is an invalid record. + * @param confidence confidence (0-100) + * @param lat latitude + * @param lng longitude + * @param time Time of the original location reading from the server + */ + public Record(int accuracy, int confidence, double lat, double lng, long time) { + this.accuracy = accuracy; + this.confidence = confidence; + this.originTime = time; + this.lat = lat; + this.lng = lng; + } + + public double getLat() { + return lat; + } + + public double getLng() { + return lng; + } + + public int getAccuracy() { + return accuracy; + } + + public int getConfidence() { + return confidence; + } + + public boolean isValid() { + return accuracy > 0; + } + + public long getTime() { + return originTime; + } + + public void write(DataOutput dataOut) throws IOException { + dataOut.writeInt(accuracy); + dataOut.writeInt(confidence); + dataOut.writeDouble(lat); + dataOut.writeDouble(lng); + dataOut.writeLong(originTime); + } + + @Override + public String toString() { + return lat + "," + lng + "," + originTime +"," + accuracy + "," + confidence; + } + } + + public class Cache<T> extends LinkedHashMap { + private final long mMaxAge; + private final int mCapacity; + private final String mDir; + private final String mFile; + private long mLastSaveTime = 0; + + public Cache(String dir, String file, int capacity, long maxAge) { + super(capacity + 1, 1.1f, true); + this.mCapacity = capacity; + this.mDir = dir; + this.mFile = file; + this.mMaxAge = maxAge; + load(); + } + + private LocationCache.Record lookup(String key) { + LocationCache.Record result = (LocationCache.Record) get(key); + + if (result == null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "lookup: " + key + " failed"); + } + return null; + } + + // Cache entry needs refresh + if (result.getTime() + mMaxAge < System.currentTimeMillis()) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "lookup: " + key + " expired"); + } + return null; + } + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "lookup: " + key + " " + result.toString()); + } + + return result; + } + + private void insert(String key, LocationCache.Record record) { + remove(key); + put(key, record); + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, "insert: " + key + " " + record.toString()); + } + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + // Remove cache entries when it has more than capacity + return size() > mCapacity; + } + + private void load() { + FileInputStream istream; + try { + File f = new File(mDir, mFile); + istream = new FileInputStream(f); + } catch (FileNotFoundException e) { + // No existing DB - return new CellCache + return; + } + + DataInputStream dataInput = new DataInputStream(istream); + + try { + int version = dataInput.readUnsignedShort(); + if (version != CACHE_DB_VERSION) { + // Ignore records - invalid version ID. + dataInput.close(); + return; + } + int records = dataInput.readUnsignedShort(); + + for (int i = 0; i < records; i++) { + final String key = dataInput.readUTF(); + final LocationCache.Record record = LocationCache.Record.read(dataInput); + //Log.d(TAG, key + " " + record.toString()); + put(key, record); + } + + dataInput.close(); + } catch (IOException e) { + // Something's corrupted - return a new CellCache + } + } + + private void save() { + long now = System.currentTimeMillis(); + if (mLastSaveTime != 0 && (now - mLastSaveTime < SAVE_FREQUENCY)) { + // Don't save to file more often than SAVE_FREQUENCY + return; + } + + FileOutputStream ostream; + + File systemDir = new File(mDir); + if (!systemDir.exists()) { + if (!systemDir.mkdirs()) { + Log.e(TAG, "Cache.save(): couldn't create directory"); + return; + } + } + + try { + File f = new File(mDir, mFile); + ostream = new FileOutputStream(f); + } catch (FileNotFoundException e) { + Log.d(TAG, "Cache.save(): unable to create cache file", e); + return; + } + + DataOutputStream dataOut = new DataOutputStream(ostream); + try { + dataOut.writeShort(CACHE_DB_VERSION); + + dataOut.writeShort(size()); + + for (Iterator iter = entrySet().iterator(); iter.hasNext();) { + Map.Entry entry = (Map.Entry) iter.next(); + String key = (String) entry.getKey(); + LocationCache.Record record = (LocationCache.Record) entry.getValue(); + dataOut.writeUTF(key); + record.write(dataOut); + } + + dataOut.close(); + mLastSaveTime = now; + + } catch (IOException e) { + Log.e(TAG, "Cache.save(): unable to write cache", e); + // This should never happen + } + } + } + + public class LocationCentroid { + + double mLatSum = 0; + double mLngSum = 0; + int mNumber = 0; + int mConfidenceSum = 0; + + double mCentroidLat = 0; + double mCentroidLng = 0; + + // Probably never have to calculate centroid for more than 10 locations + final static int MAX_SIZE = 10; + double[] mLats = new double[MAX_SIZE]; + double[] mLngs = new double[MAX_SIZE]; + int[] mRadii = new int[MAX_SIZE]; + + LocationCentroid() { + reset(); + } + + public void reset() { + mLatSum = 0; + mLngSum = 0; + mNumber = 0; + mConfidenceSum = 0; + + mCentroidLat = 0; + mCentroidLng = 0; + + for (int i = 0; i < MAX_SIZE; i++) { + mLats[i] = 0; + mLngs[i] = 0; + mRadii[i] = 0; + } + } + + public void addLocation(double lat, double lng, int accuracy, int confidence) { + if (mNumber < MAX_SIZE && accuracy <= MAX_ACCURACY_ALLOWED) { + mLatSum += lat; + mLngSum += lng; + mConfidenceSum += confidence; + + mLats[mNumber] = lat; + mLngs[mNumber] = lng; + mRadii[mNumber] = accuracy; + mNumber++; + } + } + + public int getNumber() { + return mNumber; + } + + public double getCentroidLat() { + if (mCentroidLat == 0 && mNumber != 0) { + mCentroidLat = mLatSum/mNumber; + } + return mCentroidLat; + } + + public double getCentroidLng() { + if (mCentroidLng == 0 && mNumber != 0) { + mCentroidLng = mLngSum/mNumber; + } + return mCentroidLng; + } + + public int getConfidence() { + if (mNumber != 0) { + return mConfidenceSum/mNumber; + } else { + return 0; + } + } + + public int getAccuracy() { + if (mNumber == 0) { + return 0; + } + + if (mNumber == 1) { + return mRadii[0]; + } + + double cLat = getCentroidLat(); + double cLng = getCentroidLng(); + + int meanDistanceSum = 0; + int meanRadiiSum = 0; + int smallestCircle = MAX_ACCURACY_ALLOWED; + int smallestCircleDistance = MAX_ACCURACY_ALLOWED; + float[] distance = new float[1]; + boolean outlierExists = false; + + for (int i = 0; i < mNumber; i++) { + Location.distanceBetween(cLat, cLng, mLats[i], mLngs[i], distance); + meanDistanceSum += (int)distance[0]; + if (distance[0] > mRadii[i]) { + outlierExists = true; + } + if (mRadii[i] < smallestCircle) { + smallestCircle = mRadii[i]; + smallestCircleDistance = (int)distance[0]; + } + meanRadiiSum += mRadii[i]; + } + + if (outlierExists) { + return (meanDistanceSum + meanRadiiSum)/mNumber; + } else { + return Math.max(smallestCircle, smallestCircleDistance); + } + } + + } + + private String getCellCacheKey(int mcc, int mnc, int lac, int cid) { + return mcc + ":" + mnc + ":" + lac + ":" + cid; + } + +} diff --git a/location/java/com/android/internal/location/LocationCollector.java b/location/java/com/android/internal/location/LocationCollector.java new file mode 100644 index 0000000..3d2f6bf --- /dev/null +++ b/location/java/com/android/internal/location/LocationCollector.java @@ -0,0 +1,499 @@ +/* + * 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 com.android.internal.location; + +import com.android.internal.location.protocol.GDebugProfile; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import android.location.Location; +import android.net.wifi.ScanResult; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Log; + +/** + * Listens for GPS and cell/wifi changes and anonymously uploads to server for + * improving quality of service of NetworkLocationProvider. This service is only enabled when + * the user has enabled the network location provider. + * + * {@hide} + */ +public class LocationCollector { + + private static final String TAG = "LocationCollector"; + + // last location valid for 12 minutes + private static final long MIN_VALID_LOCATION_TIME = 12 * 60 * 1000L; + + // don't send wifi more than every 10 min + private static final long MIN_TIME_BETWEEN_WIFI_REPORTS = 10 * 60 * 1000L; + + // atleast 5 changed APs for wifi collection + private static final int MIN_CHANGED_WIFI_POINTS = 5; + + // don't collect if distance moved less than 200 meters + private static final int MIN_DISTANCE_BETWEEN_REPORTS = 200; + + // don't collect if battery level less than 20% + private static final double MIN_BATTERY_LEVEL = 0.2; + + // if battery level is greater than 90% and plugged in, collect more frequently + private static final double CHARGED_BATTERY_LEVEL = 0.9; + + // collect bursts every 15 minutes (running on battery) + private static final long BURST_REST_TIME_ON_BATTERY = 15 * 60 * 1000L; + + // collect bursts every 8 minutes (when plugged in) + private static final long BURST_REST_TIME_PLUGGED = 8 * 60 * 1000L; + + // collect burst samples every 12 seconds + private static final int BURST_MEASUREMENT_INTERVAL = 12 * 1000; + + // collect 11 burst samples before resting (11 samples every 12 seconds = 2 minute bursts) + private static final int BURST_NUM_SAMPLES = 11; + + // don't collect bursts if user in same loc for 2 bursts + private static final int MAX_BURSTS_FROM_SAME_LOCATION = 2; + + // don't send more than 2 bursts if user hasn't moved more than 25 meters + private static final int MIN_DISTANCE_BETWEEN_BURSTS = 25; + + // Cell State + private CellState mCellState = null; + private CellUploads mCellUploads = new CellUploads(); + + // GPS state + private Location mLastKnownLocation = null; + private Location mLastUploadedLocation = null; + private long mLastKnownLocationTime = 0; + private long mLastUploadedLocationTime = 0; + + // Burst state + private Location mLastBurstLocation = null; + private long mLastBurstEndTime = 0; + private long mCurrentBurstStartTime = 0; + private int mCurrentBurstNumSamples = 0; + private int mNumBurstsFromLastLocation = 0; + + // WiFi state + private List<ScanResult> mWifiLastScanResults = null; + private List<ScanResult> mWifiCurrentScanResults = null; + private long mLastWifiScanElapsedTime = 0; + private long mLastWifiScanRealTime = 0; + private boolean mWifiUploadedWithoutLocation = false; + + // Collection state + private boolean mNetworkProviderIsEnabled = true; + private boolean mBatteryLevelIsHealthy = true; + private boolean mBatteryChargedAndPlugged = false; + + // Location masf service + private LocationMasfClient mMasfClient; + + public LocationCollector(LocationMasfClient masfClient) { + mMasfClient = masfClient; + } + + /** + * Updates cell tower state. This is usually always up to date so should be uploaded + * each time a new location is available. + * + * @param newState cell state + */ + public synchronized void updateCellState(CellState newState) { + if (newState == null) { + throw new IllegalArgumentException("cell state is null"); + } + + if (!newState.isValid()) { + return; + } + + if (mCellState != null && mCellState.equals(newState)) { + return; + } + + mCellState = newState; + log("updateCellState(): Updated to " + mCellState.getCid() + "," + mCellState.getLac()); + + if (isCollectionEnabled()) { + addToQueue(GDebugProfile.TRIGGER_CELL_CHANGE); + } + } + + /** + * Updates GPS location if collection is enabled + * + * @param location location object + */ + public synchronized void updateLocation(Location location) { + + // Don't do anything if collection is disabled + if (!isCollectionEnabled()) { + return; + } + + long now = SystemClock.elapsedRealtime(); + + // Update last known location + if (mLastKnownLocation == null) { + mLastKnownLocation = new Location(location); + } else { + mLastKnownLocation.set(location); + } + mLastKnownLocationTime = now; + + // Burst rest time depends on battery state + long restTime = BURST_REST_TIME_ON_BATTERY; + if (mBatteryChargedAndPlugged) { + restTime = BURST_REST_TIME_PLUGGED; + } + + int trigger; + + // In burst mode if either first burst or enough time has passed since last burst + if (mLastBurstEndTime == 0 || (now - mLastBurstEndTime > restTime)) { + + // If location is too recent, then don't do anything! + if (now - mLastUploadedLocationTime < BURST_MEASUREMENT_INTERVAL) { + return; + } + + int distanceFromLastBurst = -1; + if (mLastBurstLocation != null) { + distanceFromLastBurst = (int) mLastBurstLocation.distanceTo(location); + + // Too many bursts from same location, don't upload + if (distanceFromLastBurst < MIN_DISTANCE_BETWEEN_BURSTS && + mNumBurstsFromLastLocation >= MAX_BURSTS_FROM_SAME_LOCATION) { + log("NO UPLOAD: Too many bursts from same location."); + return; + } + } + + if (mCurrentBurstStartTime == 0) { + // Start the burst! + mCurrentBurstStartTime = now; + mCurrentBurstNumSamples = 1; + trigger = GDebugProfile.TRIGGER_COLLECTION_START_BURST; + + } else if (now - mCurrentBurstStartTime > restTime) { + // Burst got old, start a new one + mCurrentBurstStartTime = now; + mCurrentBurstNumSamples = 1; + trigger = GDebugProfile.TRIGGER_COLLECTION_RESTART_BURST; + + } else if (mCurrentBurstNumSamples == BURST_NUM_SAMPLES - 1) { + // Finished a burst + mLastBurstEndTime = now; + mCurrentBurstStartTime = 0; + mCurrentBurstNumSamples = 0; + + // Make sure we don't upload too many bursts from same location + if (mLastBurstLocation == null) { + mLastBurstLocation = new Location(location); + mNumBurstsFromLastLocation = 1; + trigger = GDebugProfile.TRIGGER_COLLECTION_END_BURST; + + } else { + + if (distanceFromLastBurst != -1 && + distanceFromLastBurst < MIN_DISTANCE_BETWEEN_BURSTS) { + // User hasnt moved much from last location, keep track of count, + // don't update last burst loc + mNumBurstsFromLastLocation++; + trigger = GDebugProfile.TRIGGER_COLLECTION_END_BURST_AT_SAME_LOCATION; + + } else { + // User has moved enough, update last burst loc + mLastBurstLocation.set(location); + mNumBurstsFromLastLocation = 1; + trigger = GDebugProfile.TRIGGER_COLLECTION_END_BURST; + } + } + + } else { + // Increment burst sample count + mCurrentBurstNumSamples++; + trigger = GDebugProfile.TRIGGER_COLLECTION_CONTINUE_BURST; + } + + } else if (mLastUploadedLocation != null + && (mLastUploadedLocation.distanceTo(location) > MIN_DISTANCE_BETWEEN_REPORTS)) { + // If not in burst mode but has moved a reasonable distance, upload! + trigger = GDebugProfile.TRIGGER_COLLECTION_MOVED_DISTANCE; + + } else { + // Not in burst mode or hasn't moved enough + log("NO UPLOAD: Not in burst or moving mode. Resting for " + restTime + " ms"); + return; + } + + log("updateLocation(): Updated location with trigger " + trigger); + addToQueue(trigger); + } + + /** + * Updates wifi scan results if collection is enabled + * + * @param currentScanResults scan results + */ + public synchronized void updateWifiScanResults(List<ScanResult> currentScanResults) { + if (!isCollectionEnabled()) { + return; + } + + if (currentScanResults == null || currentScanResults.size() == 0) { + return; + } + + long now = SystemClock.elapsedRealtime(); + + // If wifi scan recently received, then don't upload + if ((mLastWifiScanElapsedTime != 0) + && ((now - mLastWifiScanElapsedTime) <= MIN_TIME_BETWEEN_WIFI_REPORTS)) { + return; + } + + if (mWifiCurrentScanResults == null) { + mWifiCurrentScanResults = new ArrayList<ScanResult>(); + } else { + mWifiCurrentScanResults.clear(); + } + mWifiCurrentScanResults.addAll(currentScanResults); + + // If wifi has changed enough + boolean wifiHasChanged = false; + + if (mWifiLastScanResults == null) { + wifiHasChanged = true; + } else { + // Calculate the number of new AP points received + HashSet<String> previous = new HashSet<String>(); + HashSet<String> current = new HashSet<String>(); + for (ScanResult s : mWifiLastScanResults) { + previous.add(s.BSSID); + } + for (ScanResult s : mWifiCurrentScanResults) { + current.add(s.BSSID); + } + current.removeAll(previous); + + if (current.size() > + Math.min(MIN_CHANGED_WIFI_POINTS, ((mWifiCurrentScanResults.size()+1)/2))) { + wifiHasChanged = true; + } + } + + if (!wifiHasChanged) { + log("updateWifiScanResults(): Wifi results haven't changed much"); + return; + } + + if (mWifiLastScanResults == null) { + mWifiLastScanResults = new ArrayList<ScanResult>(); + } else { + mWifiLastScanResults.clear(); + } + mWifiLastScanResults.addAll(mWifiCurrentScanResults); + + mLastWifiScanElapsedTime = now; + mLastWifiScanRealTime = System.currentTimeMillis(); + + log("updateWifiScanResults(): Updated " + mWifiLastScanResults.size() + " APs"); + addToQueue(GDebugProfile.TRIGGER_WIFI_CHANGE); + } + + /** + * Updates the status of the network location provider. + * + * @param enabled true if user has enabled network location based on Google's database + * of wifi points and cell towers. + */ + public void updateNetworkProviderStatus(boolean enabled) { + mNetworkProviderIsEnabled = enabled; + } + + /** + * Updates the battery health. Battery level is healthy if there is greater than + * {@link #MIN_BATTERY_LEVEL} percentage left or if the device is plugged in + * + * @param scale maximum scale for battery + * @param level current level + * @param plugged true if device is plugged in + */ + public void updateBatteryState(int scale, int level, boolean plugged) { + mBatteryLevelIsHealthy = (plugged || (level >= (MIN_BATTERY_LEVEL * scale))); + mBatteryChargedAndPlugged = (plugged && (level >= (CHARGED_BATTERY_LEVEL * scale))); + } + + /** + * Anonymous data collection is only enabled when the user has enabled the network + * location provider, i.e. is making use of the service and if the device battery level + * is healthy. + * + * Additionally, data collection will *never* happen if the system + * property ro.com.google.enable_google_location_features is not set. + * + * @return true if anonymous location collection is enabled + */ + private boolean isCollectionEnabled() { + // This class provides a Google-specific location feature, so it's enabled only + // when the system property ro.com.google.enable_google_location_features is set. + if (!SystemProperties.get("ro.com.google.enable_google_location_features").equals("1")) { + return false; + } + return mBatteryLevelIsHealthy && mNetworkProviderIsEnabled; + } + + /** + * Adds to the MASF request queue + * + * @param trigger the event that triggered this collection event + */ + private synchronized void addToQueue(int trigger) { + + long now = SystemClock.elapsedRealtime(); + + // Include location if: + // It has been received in the last 12 minutes. + boolean includeLocation = false; + if (mLastKnownLocation != null && + (now - mLastKnownLocationTime <= MIN_VALID_LOCATION_TIME)) { + includeLocation = true; + } + + // Include wifi if: + // Wifi is new OR + // Wifi is old but last wifi upload was without location + boolean includeWifi = false; + if (trigger == GDebugProfile.TRIGGER_WIFI_CHANGE || (mWifiUploadedWithoutLocation && + includeLocation && (now - mLastWifiScanElapsedTime < MIN_VALID_LOCATION_TIME))) { + includeWifi = true; + mWifiUploadedWithoutLocation = !includeLocation; + } + + // Include cell if: + // Wifi or location information is already being included + // The cell hasn't been uploaded with the same location recently + boolean includeCell = false; + + if (mCellState != null && (includeWifi || includeLocation)) { + includeCell = true; + + if (!includeWifi && includeLocation) { + if (mCellUploads.contains(mCellState, mLastKnownLocation)) { + includeCell = false; + } + } + } + + if (!includeLocation && !includeWifi) { + log("NO UPLOAD: includeLocation=false, includeWifi=false"); + return; + } else if (!includeCell && trigger == GDebugProfile.TRIGGER_CELL_CHANGE) { + log("NO UPLOAD: includeCell=false"); + return; + } else { + log("UPLOAD: includeLocation=" + includeLocation + ", includeWifi=" + + includeWifi + ", includeCell=" + includeCell); + } + + if (includeLocation) { + // Update last uploaded location + if (mLastUploadedLocation == null) { + mLastUploadedLocation = new Location(mLastKnownLocation); + } else { + mLastUploadedLocation.set(mLastKnownLocation); + } + mLastUploadedLocationTime = now; + } + + // Immediately send output if finishing a burst for live traffic requirements + boolean immediate = false; + if (trigger == GDebugProfile.TRIGGER_COLLECTION_END_BURST|| + trigger == GDebugProfile.TRIGGER_COLLECTION_END_BURST_AT_SAME_LOCATION) { + immediate = true; + } + + try { + CellState cell = includeCell ? mCellState : null; + List<ScanResult> wifi = includeWifi ? mWifiLastScanResults : null; + Location loc = includeLocation ? mLastUploadedLocation : null; + + mMasfClient.queueCollectionReport( + trigger, loc, cell, wifi, mLastWifiScanRealTime, immediate); + + } catch(Exception e) { + Log.e(TAG, "addToQueue got exception:", e); + } + } + + private class CellUploads { + + private final int MIN_DISTANCE = MIN_DISTANCE_BETWEEN_REPORTS / 4; // 50 meters + private final int SIZE = 5; + private final String[] cells = new String[SIZE]; + private final boolean[] valid = new boolean[SIZE]; + private final double[] latitudes = new double[SIZE]; + private final double[] longitudes = new double[SIZE]; + private final float[] distance = new float[1]; + private int index = 0; + + private CellUploads() { + for (int i = 0; i < SIZE; i++) { + valid[i] = false; + } + } + + private boolean contains(CellState cellState, Location loc) { + String cell = + cellState.getCid() + ":" + cellState.getLac() + ":" + + cellState.getMnc() + ":" + cellState.getMcc(); + double lat = loc.getLatitude(); + double lng = loc.getLongitude(); + + for (int i = 0; i < SIZE; i++) { + if (valid[i] && cells[i].equals(cell)) { + Location.distanceBetween(latitudes[i], longitudes[i], lat, lng, distance); + if (distance[0] < MIN_DISTANCE) { + return true; + } + } + } + cells[index] = cell; + latitudes[index] = lat; + longitudes[index] = lng; + valid[index] = true; + + index++; + if (index == SIZE) { + index = 0; + } + return false; + } + } + + private void log(String string) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.d(TAG, string); + } + } +} diff --git a/location/java/com/android/internal/location/LocationMasfClient.java b/location/java/com/android/internal/location/LocationMasfClient.java new file mode 100644 index 0000000..ed9f3d6 --- /dev/null +++ b/location/java/com/android/internal/location/LocationMasfClient.java @@ -0,0 +1,1103 @@ +// Copyright 2008 The Android Open Source Project + +package com.android.internal.location; + +import com.google.common.Config; +import com.google.common.android.AndroidConfig; +import com.google.common.io.protocol.ProtoBuf; +import com.google.masf.MobileServiceMux; +import com.google.masf.ServiceCallback; +import com.google.masf.protocol.PlainRequest; +import com.google.masf.protocol.Request; + +import com.android.internal.location.protocol.GAddress; +import com.android.internal.location.protocol.GAddressComponent; +import com.android.internal.location.protocol.GAppProfile; +import com.android.internal.location.protocol.GCell; +import com.android.internal.location.protocol.GCellularPlatformProfile; +import com.android.internal.location.protocol.GCellularProfile; +import com.android.internal.location.protocol.GDebugProfile; +import com.android.internal.location.protocol.GDeviceLocation; +import com.android.internal.location.protocol.GFeature; +import com.android.internal.location.protocol.GGeocodeRequest; +import com.android.internal.location.protocol.GLatLng; +import com.android.internal.location.protocol.GLocReply; +import com.android.internal.location.protocol.GLocReplyElement; +import com.android.internal.location.protocol.GLocRequest; +import com.android.internal.location.protocol.GLocRequestElement; +import com.android.internal.location.protocol.GLocation; +import com.android.internal.location.protocol.GPlatformProfile; +import com.android.internal.location.protocol.GPrefetchMode; +import com.android.internal.location.protocol.GRectangle; +import com.android.internal.location.protocol.GWifiDevice; +import com.android.internal.location.protocol.GWifiProfile; +import com.android.internal.location.protocol.GcellularMessageTypes; +import com.android.internal.location.protocol.GdebugprofileMessageTypes; +import com.android.internal.location.protocol.GlatlngMessageTypes; +import com.android.internal.location.protocol.GlocationMessageTypes; +import com.android.internal.location.protocol.GrectangleMessageTypes; +import com.android.internal.location.protocol.GwifiMessageTypes; +import com.android.internal.location.protocol.LocserverMessageTypes; +import com.android.internal.location.protocol.ResponseCodes; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; + +import android.content.Context; +import android.location.Address; +import android.location.Location; +import android.location.LocationManager; +import android.net.wifi.ScanResult; +import android.os.Build; +import android.os.Bundle; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; + +/** + * Service to communicate to the Google Location Server (GLS) via MASF server + * + * {@hide} + */ +public class LocationMasfClient { + + static final String TAG = "LocationMasfClient"; + + // Address of the MASF server to connect to. + private static final String MASF_SERVER_ADDRESS = "http://www.google.com/loc/m/api"; + + // MobileServiceMux app/platform-specific values (application name matters!) + private static final String APPLICATION_NAME = "location"; + private static final String APPLICATION_VERSION = "1.0"; + private static final String PLATFORM_ID = "android"; + private static final String DISTRIBUTION_CHANNEL = "android"; + private static String PLATFORM_BUILD = null; + + // Methods exposed by the MASF server + private static final String REQUEST_QUERY_LOC = "g:loc/ql"; + private static final String REQUEST_UPLOAD_LOC = "g:loc/ul"; + + // Max time to wait for request to end + private static final long REQUEST_TIMEOUT = 5000; + + // Constant to divide Lat, Lng returned by server + private static final double E7 = 10000000.0; + + // Max wifi points to include + private static final int MAX_WIFI_TO_INCLUDE = 25; + + // Location of GLS cookie + private static final String PLATFORM_KEY_FILE = "gls.platform.key"; + private String mPlatformKey; + + // Cell cache + private LocationCache mLocationCache; + + // Location object that the cache manages + private Location mLocation = new Location(LocationManager.NETWORK_PROVIDER); + + // ProtoBuf objects we can reuse for subsequent requests + private final int MAX_COLLECTION_BUFFER_SIZE = 30; + private final long MIN_COLLECTION_INTERVAL = 15 * 60 * 1000; // 15 minutes + private ProtoBuf mPlatformProfile = null; + private ProtoBuf mCellularPlatformProfile = null; + private ProtoBuf mCurrentCollectionRequest = null; + private long mLastCollectionUploadTime = 0; + + // Objects for current request + private List<ScanResult> mWifiScanResults = new ArrayList<ScanResult>(); + private CellState mCellState = null; + private List<CellState> mCellHistory; + + // This tag is used for the event log. + private static final int COLLECTION_EVENT_LOG_TAG = 2740; + + // Extra values to designate whether location is from cache or network request + private static final String EXTRA_KEY_LOCATION_SOURCE = "networkLocationSource"; + private static final String EXTRA_VALUE_LOCATION_SOURCE_CACHED = "cached"; + private static final String EXTRA_VALUE_LOCATION_SOURCE_SERVER = "server"; + + /** + * Initializes the MobileServiceMux. Must be called before using any other function in the + * class. + */ + public LocationMasfClient(Context context) { + MobileServiceMux mux = MobileServiceMux.getSingleton(); + if (mux == null) { + AndroidConfig config = new AndroidConfig(context); + Config.setConfig(config); + + MobileServiceMux.initialize + (MASF_SERVER_ADDRESS, + APPLICATION_NAME, + APPLICATION_VERSION, + PLATFORM_ID, + DISTRIBUTION_CHANNEL); + } + mLocationCache = new LocationCache(); + + if (Build.FINGERPRINT != null) { + PLATFORM_BUILD = PLATFORM_ID + "/" + Build.FINGERPRINT; + } else { + PLATFORM_BUILD = PLATFORM_ID; + } + } + + /** + * Returns the location for the given cell or wifi information. + * + * @param apps list of apps requesting location + * @param trigger event that triggered this network request + * @param cellState cell tower state + * @param cellHistory history of acquired cell states + * @param scanResults list of wifi scan results + * @param scanTime time at which wireless scan was triggered + * @param callback function to call with received location + */ + public synchronized void getNetworkLocation(Collection<String> apps, int trigger, + CellState cellState, List<CellState> cellHistory, List<ScanResult> scanResults, + long scanTime, NetworkLocationProvider.Callback callback) { + + final NetworkLocationProvider.Callback finalCallback = callback; + + boolean foundInCache = + mLocationCache.lookup(cellState, cellHistory, scanResults, mLocation); + + if (foundInCache) { + + if (SystemClock.elapsedRealtime() - mLastCollectionUploadTime > MIN_COLLECTION_INTERVAL) { + uploadCollectionReport(true); + } + + Bundle extras = mLocation.getExtras() == null ? new Bundle() : mLocation.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_SOURCE, EXTRA_VALUE_LOCATION_SOURCE_CACHED); + mLocation.setExtras(extras); + + Log.d(TAG, "getNetworkLocation(): Returning cache location with accuracy " + + mLocation.getAccuracy()); + finalCallback.locationReceived(mLocation, true); + return; + } + + Log.d(TAG, "getNetworkLocation(): Location not found in cache, making network request"); + + // Copy over to objects for this request + mWifiScanResults.clear(); + if (scanResults != null) { + mWifiScanResults.addAll(scanResults); + } + mCellState = cellState; + mCellHistory = cellHistory; + + // Create a RequestElement + ProtoBuf requestElement = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST_ELEMENT); + + // Debug profile + if (trigger != -1) { + ProtoBuf debugProfile = new ProtoBuf(GdebugprofileMessageTypes.GDEBUG_PROFILE); + debugProfile.setInt(GDebugProfile.TRIGGER, trigger); + requestElement.setProtoBuf(GLocRequestElement.DEBUG_PROFILE, debugProfile); + } + + // Cellular profile + if (mCellState != null && mCellState.isValid()) { + ProtoBuf cellularProfile = new ProtoBuf(GcellularMessageTypes.GCELLULAR_PROFILE); + cellularProfile.setLong(GCellularProfile.TIMESTAMP, mCellState.getTime()); + cellularProfile.setInt(GCellularProfile.PREFETCH_MODE, + GPrefetchMode.PREFETCH_MODE_MORE_NEIGHBORS); + + // Primary cell + ProtoBuf primaryCell = new ProtoBuf(GcellularMessageTypes.GCELL); + primaryCell.setInt(GCell.LAC, mCellState.getLac()); + primaryCell.setInt(GCell.CELLID, mCellState.getCid()); + + if ((mCellState.getMcc() != -1) && (mCellState.getMnc() != -1)) { + primaryCell.setInt(GCell.MCC, mCellState.getMcc()); + primaryCell.setInt(GCell.MNC, mCellState.getMnc()); + } + cellularProfile.setProtoBuf(GCellularProfile.PRIMARY_CELL, primaryCell); + + // History of cells + for (CellState c : cellHistory) { + ProtoBuf pastCell = new ProtoBuf(GcellularMessageTypes.GCELL); + pastCell.setInt(GCell.LAC, c.getLac()); + pastCell.setInt(GCell.CELLID, c.getCid()); + if ((c.getMcc() != -1) && (c.getMnc() != -1)) { + pastCell.setInt(GCell.MCC, c.getMcc()); + pastCell.setInt(GCell.MNC, c.getMnc()); + } + pastCell.setInt(GCell.AGE, (int)(mCellState.getTime() - c.getTime())); + cellularProfile.addProtoBuf(GCellularProfile.HISTORICAL_CELLS, pastCell); + } + + requestElement.setProtoBuf(GLocRequestElement.CELLULAR_PROFILE, cellularProfile); + } + + // Wifi profile + if (mWifiScanResults != null && mWifiScanResults.size() > 0) { + ProtoBuf wifiProfile = new ProtoBuf(GwifiMessageTypes.GWIFI_PROFILE); + wifiProfile.setLong(GWifiProfile.TIMESTAMP, scanTime); + wifiProfile.setInt(GWifiProfile.PREFETCH_MODE, + GPrefetchMode.PREFETCH_MODE_MORE_NEIGHBORS); + + int count = 0; + for (ScanResult s : mWifiScanResults) { + ProtoBuf wifiDevice = new ProtoBuf(GwifiMessageTypes.GWIFI_DEVICE); + wifiDevice.setString(GWifiDevice.MAC, s.BSSID); + wifiProfile.addProtoBuf(GWifiProfile.WIFI_DEVICES, wifiDevice); + count++; + if (count >= MAX_WIFI_TO_INCLUDE) { + break; + } + } + + requestElement.setProtoBuf(GLocRequestElement.WIFI_PROFILE, wifiProfile); + } + + // Request to send over wire + ProtoBuf request = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST); + request.addProtoBuf(GLocRequest.REQUEST_ELEMENTS, requestElement); + + // Create a Platform Profile + ProtoBuf platformProfile = createPlatformProfile(); + if (mCellState != null && mCellState.isValid()) { + // Include cellular platform Profile + ProtoBuf cellularPlatform = createCellularPlatformProfile(mCellState); + platformProfile.setProtoBuf(GPlatformProfile.CELLULAR_PLATFORM_PROFILE, + cellularPlatform); + } + request.setProtoBuf(GLocRequest.PLATFORM_PROFILE, platformProfile); + + // Include App Profiles + if (apps != null) { + for (String app : apps) { + ProtoBuf appProfile = new ProtoBuf(GlocationMessageTypes.GAPP_PROFILE); + appProfile.setString(GAppProfile.APP_NAME, app); + request.addProtoBuf(GLocRequest.APP_PROFILES, appProfile); + } + } + + // Queue any waiting collection events as well + uploadCollectionReport(false); + + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + try { + request.outputTo(payload); + } catch (IOException e) { + Log.e(TAG, "getNetworkLocation(): unable to write request to payload", e); + return; + } + + // Creates request and a listener with a call back function + ProtoBuf reply = new ProtoBuf(LocserverMessageTypes.GLOC_REPLY); + Request plainRequest = + new PlainRequest(REQUEST_QUERY_LOC, (short)0, payload.toByteArray()); + + ProtoRequestListener listener = new ProtoRequestListener(reply, new ServiceCallback() { + public void onRequestComplete(Object result) { + ProtoBuf response = (ProtoBuf) result; + boolean successful = parseNetworkLocationReply(response); + finalCallback.locationReceived(mLocation, successful); + + } + }); + plainRequest.setListener(listener); + + // Send request + MobileServiceMux serviceMux = MobileServiceMux.getSingleton(); + serviceMux.submitRequest(plainRequest, true); + } + + private synchronized boolean parseNetworkLocationReply(ProtoBuf response) { + if (response == null) { + Log.e(TAG, "getNetworkLocation(): response is null"); + return false; + } + + int status1 = response.getInt(GLocReply.STATUS); + if (status1 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "getNetworkLocation(): RPC failed with status " + status1); + return false; + } + + if (response.has(GLocReply.PLATFORM_KEY)) { + String platformKey = response.getString(GLocReply.PLATFORM_KEY); + if (!TextUtils.isEmpty(platformKey)) { + setPlatformKey(platformKey); + } + } + + if (!response.has(GLocReply.REPLY_ELEMENTS)) { + Log.e(TAG, "getNetworkLocation(): no ReplyElement"); + return false; + } + ProtoBuf replyElement = response.getProtoBuf(GLocReply.REPLY_ELEMENTS); + + int status2 = replyElement.getInt(GLocReplyElement.STATUS); + if (status2 != ResponseCodes.STATUS_STATUS_SUCCESS && + status2 != ResponseCodes.STATUS_STATUS_FAILED) { + Log.e(TAG, "getNetworkLocation(): GLS failed with status " + status2); + return false; + } + + // Get prefetched data to add to the device cache + Log.d(TAG, "getNetworkLocation(): Number of prefetched entries " + + replyElement.getCount(GLocReplyElement.DEVICE_LOCATION)); + long now = System.currentTimeMillis(); + for (int i = 0; i < replyElement.getCount(GLocReplyElement.DEVICE_LOCATION); i++ ) { + ProtoBuf device = replyElement.getProtoBuf(GLocReplyElement.DEVICE_LOCATION, i); + double lat = 0; + double lng = 0; + int accuracy = -1; + int confidence = -1; + int locType = -1; + if (device.has(GDeviceLocation.LOCATION)) { + ProtoBuf deviceLocation = device.getProtoBuf(GDeviceLocation.LOCATION); + if (deviceLocation.has(GLocation.ACCURACY) && + deviceLocation.has(GLocation.LAT_LNG) + && deviceLocation.has(GLocation.CONFIDENCE)) { + lat = deviceLocation.getProtoBuf(GLocation.LAT_LNG). + getInt(GLatLng.LAT_E7) / E7; + lng = deviceLocation.getProtoBuf(GLocation.LAT_LNG). + getInt(GLatLng.LNG_E7) / E7; + accuracy = deviceLocation.getInt(GLocation.ACCURACY); + confidence = deviceLocation.getInt(GLocation.CONFIDENCE); + } + if (deviceLocation.has(GLocation.LOC_TYPE)) { + locType = deviceLocation.getInt(GLocation.LOC_TYPE); + } + } + + // Get cell key + if (device.has(GDeviceLocation.CELL) && locType != GLocation.LOCTYPE_TOWER_LOCATION) { + ProtoBuf deviceCell = device.getProtoBuf(GDeviceLocation.CELL); + int cid = deviceCell.getInt(GCell.CELLID); + int lac = deviceCell.getInt(GCell.LAC); + int mcc = -1; + int mnc = -1; + if (deviceCell.has(GCell.MNC) && deviceCell.has(GCell.MCC)) { + mcc = deviceCell.getInt(GCell.MCC); + mnc = deviceCell.getInt(GCell.MNC); + } + mLocationCache. + insert(mcc, mnc, lac, cid, lat, lng, accuracy, confidence, now); + } + + // Get wifi key + if (device.has(GDeviceLocation.WIFI_DEVICE)) { + ProtoBuf deviceWifi = device.getProtoBuf(GDeviceLocation.WIFI_DEVICE); + String bssid = deviceWifi.getString(GWifiDevice.MAC); + mLocationCache.insert(bssid, lat, lng, accuracy, confidence, now); + } + } + + mLocationCache.save(); + + // For consistent results for user, always return cache computed location + boolean foundInCache = + mLocationCache.lookup(mCellState, mCellHistory, mWifiScanResults, mLocation); + + if (foundInCache) { + + Bundle extras = mLocation.getExtras() == null ? new Bundle() : mLocation.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_SOURCE, EXTRA_VALUE_LOCATION_SOURCE_SERVER); + mLocation.setExtras(extras); + + Log.d(TAG, "getNetworkLocation(): Returning network location with accuracy " + + mLocation.getAccuracy()); + return true; + } + + if (status2 == ResponseCodes.STATUS_STATUS_FAILED) { + Log.e(TAG, "getNetworkLocation(): GLS does not have location"); + // We return true here since even though there is no location, there is no need to retry + // since server doesn't have location + return true; + } + + // Get server computed location to return for now + if (!replyElement.has(GLocReplyElement.LOCATION)) { + Log.e(TAG, "getNetworkLocation(): no location in ReplyElement"); + return false; + } + ProtoBuf location = replyElement.getProtoBuf(GLocReplyElement.LOCATION); + + if (!location.has(GLocation.LAT_LNG)) { + Log.e(TAG, "getNetworkLocation(): no Lat,Lng in location"); + return false; + } + + ProtoBuf point = location.getProtoBuf(GLocation.LAT_LNG); + double lat = point.getInt(GLatLng.LAT_E7) / E7; + double lng = point.getInt(GLatLng.LNG_E7) / E7; + + int accuracy = 0; + if (location.has(GLocation.ACCURACY)) { + accuracy = location.getInt(GLocation.ACCURACY); + } + + mLocation.setLatitude(lat); + mLocation.setLongitude(lng); + mLocation.setTime(System.currentTimeMillis()); + mLocation.setAccuracy(accuracy); + + Bundle extras = mLocation.getExtras() == null ? new Bundle() : mLocation.getExtras(); + extras.putString(EXTRA_KEY_LOCATION_SOURCE, EXTRA_VALUE_LOCATION_SOURCE_SERVER); + mLocation.setExtras(extras); + + Log.e(TAG, "getNetworkLocation(): Returning *server* computed location with accuracy " + + accuracy); + + return true; + } + + /** + * Gets a reverse geocoded location from the given lat,lng point. Also attaches the name + * of the requesting application with the request + * + * @param locale locale for geocoded location + * @param appPackageName name of the package, may be null + * @param lat latitude + * @param lng longitude + * @param maxResults maximum number of addresses to return + * @param addrs the list of addresses to fill up + * @throws IOException if network is unavailable or some other issue + */ + public void reverseGeocode(Locale locale, String appPackageName, + double lat, double lng, int maxResults, List<Address> addrs) throws IOException { + + // Reverse geocoding request element + ProtoBuf requestElement = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST_ELEMENT); + + ProtoBuf latlngElement = new ProtoBuf(GlatlngMessageTypes.GLAT_LNG); + latlngElement.setInt(GLatLng.LAT_E7, (int)(lat * E7)); + latlngElement.setInt(GLatLng.LNG_E7, (int)(lng * E7)); + + ProtoBuf locationElement = new ProtoBuf(GlocationMessageTypes.GLOCATION); + locationElement.setProtoBuf(GLocation.LAT_LNG, latlngElement); + locationElement.setLong(GLocation.TIMESTAMP, System.currentTimeMillis()); + requestElement.setProtoBuf(GLocRequestElement.LOCATION, locationElement); + + ProtoBuf geocodeElement = + new ProtoBuf(LocserverMessageTypes.GGEOCODE_REQUEST); + geocodeElement.setInt(GGeocodeRequest.NUM_FEATURE_LIMIT, maxResults); + requestElement.setProtoBuf(GLocRequestElement.GEOCODE, geocodeElement); + + // Request to send over wire + ProtoBuf request = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST); + request.addProtoBuf(GLocRequest.REQUEST_ELEMENTS, requestElement); + + // Create platform profile + ProtoBuf platformProfile = createPlatformProfile(locale); + request.setProtoBuf(GLocRequest.PLATFORM_PROFILE, platformProfile); + + // Include app name + if (appPackageName != null) { + ProtoBuf appProfile = new ProtoBuf(GlocationMessageTypes.GAPP_PROFILE); + appProfile.setString(GAppProfile.APP_NAME, appPackageName); + request.setProtoBuf(GLocRequest.APP_PROFILES, appProfile); + } + + // Queue any waiting collection events as well + uploadCollectionReport(false); + + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + try { + request.outputTo(payload); + } catch (IOException e) { + Log.e(TAG, "reverseGeocode(): unable to write request to payload"); + throw e; + } + + // Creates request and a listener with no callback function + ProtoBuf reply = new ProtoBuf(LocserverMessageTypes.GLOC_REPLY); + Request plainRequest = + new PlainRequest(REQUEST_QUERY_LOC, (short)0, payload.toByteArray()); + ProtoRequestListener listener = new ProtoRequestListener(reply, null); + plainRequest.setListener(listener); + + // Immediately send request and block for response until REQUEST_TIMEOUT + MobileServiceMux serviceMux = MobileServiceMux.getSingleton(); + serviceMux.submitRequest(plainRequest, true); + ProtoBuf response; + try { + response = (ProtoBuf)listener.getAsyncResult().get(REQUEST_TIMEOUT); + } catch (InterruptedException e) { + Log.e(TAG, "reverseGeocode(): response timeout"); + throw new IOException("response time-out"); + } + + if (response == null) { + throw new IOException("Unable to parse response from server"); + } + + // Parse the response + int status1 = response.getInt(GLocReply.STATUS); + if (status1 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "reverseGeocode(): RPC failed with status " + status1); + throw new IOException("RPC failed with status " + status1); + } + + if (response.has(GLocReply.PLATFORM_KEY)) { + String platformKey = response.getString(GLocReply.PLATFORM_KEY); + if (!TextUtils.isEmpty(platformKey)) { + setPlatformKey(platformKey); + } + } + + if (!response.has(GLocReply.REPLY_ELEMENTS)) { + Log.e(TAG, "reverseGeocode(): no ReplyElement"); + return; + } + ProtoBuf replyElement = response.getProtoBuf(GLocReply.REPLY_ELEMENTS); + + int status2 = replyElement.getInt(GLocReplyElement.STATUS); + if (status2 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "reverseGeocode(): GLS failed with status " + status2); + return; + } + + if (!replyElement.has(GLocReplyElement.LOCATION)) { + Log.e(TAG, "reverseGeocode(): no location in ReplyElement"); + return; + } + + ProtoBuf location = replyElement.getProtoBuf(GLocReplyElement.LOCATION); + if (!location.has(GLocation.FEATURE)) { + Log.e(TAG, "reverseGeocode(): no feature in GLocation"); + return; + } + + getAddressFromProtoBuf(location, locale, addrs); + } + + /** + * Gets a forward geocoded location from the given location string. Also attaches the name + * of the requesting application with the request + * + * Optionally, can specify the bounding box that the search results should be restricted to + * + * @param locale locale for geocoded location + * @param appPackageName name of the package, may be null + * @param locationString string to forward geocode + * @param lowerLeftLatitude latitude of lower left point of bounding box + * @param lowerLeftLongitude longitude of lower left point of bounding box + * @param upperRightLatitude latitude of upper right point of bounding box + * @param upperRightLongitude longitude of upper right point of bounding box + * @param maxResults maximum number of results to return + * @param addrs the list of addresses to fill up + * @throws IOException if network is unavailable or some other issue + */ + public void forwardGeocode(Locale locale, String appPackageName, String locationString, + double lowerLeftLatitude, double lowerLeftLongitude, + double upperRightLatitude, double upperRightLongitude, int maxResults, List<Address> addrs) + throws IOException { + + // Forward geocoding request element + ProtoBuf requestElement = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST_ELEMENT); + + ProtoBuf locationElement = new ProtoBuf(GlocationMessageTypes.GLOCATION); + locationElement.setLong(GLocation.TIMESTAMP, System.currentTimeMillis()); + locationElement.setString(GLocation.LOCATION_STRING, locationString); + requestElement.setProtoBuf(GLocRequestElement.LOCATION, locationElement); + + ProtoBuf geocodeElement = + new ProtoBuf(LocserverMessageTypes.GGEOCODE_REQUEST); + geocodeElement.setInt(GGeocodeRequest.NUM_FEATURE_LIMIT, maxResults); + + if (lowerLeftLatitude != 0 && lowerLeftLongitude !=0 && + upperRightLatitude !=0 && upperRightLongitude !=0) { + ProtoBuf lowerLeft = new ProtoBuf(GlatlngMessageTypes.GLAT_LNG); + lowerLeft.setInt(GLatLng.LAT_E7, (int)(lowerLeftLatitude * E7)); + lowerLeft.setInt(GLatLng.LNG_E7, (int)(lowerLeftLongitude * E7)); + + ProtoBuf upperRight = new ProtoBuf(GlatlngMessageTypes.GLAT_LNG); + upperRight.setInt(GLatLng.LAT_E7, (int)(upperRightLatitude * E7)); + upperRight.setInt(GLatLng.LNG_E7, (int)(upperRightLongitude * E7)); + + ProtoBuf boundingBox = new ProtoBuf(GrectangleMessageTypes.GRECTANGLE); + boundingBox.setProtoBuf(GRectangle.LOWER_LEFT, lowerLeft); + boundingBox.setProtoBuf(GRectangle.UPPER_RIGHT, upperRight); + geocodeElement.setProtoBuf(GGeocodeRequest.BOUNDING_BOX, boundingBox); + } + requestElement.setProtoBuf(GLocRequestElement.GEOCODE, geocodeElement); + + // Request to send over wire + ProtoBuf request = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST); + request.addProtoBuf(GLocRequest.REQUEST_ELEMENTS, requestElement); + + // Create platform profile + ProtoBuf platformProfile = createPlatformProfile(locale); + request.setProtoBuf(GLocRequest.PLATFORM_PROFILE, platformProfile); + + // Include app name + if (appPackageName != null) { + ProtoBuf appProfile = new ProtoBuf(GlocationMessageTypes.GAPP_PROFILE); + appProfile.setString(GAppProfile.APP_NAME, appPackageName); + request.setProtoBuf(GLocRequest.APP_PROFILES, appProfile); + } + + // Queue any waiting collection events as well + uploadCollectionReport(false); + + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + try { + request.outputTo(payload); + } catch (IOException e) { + Log.e(TAG, "forwardGeocode(): unable to write request to payload"); + throw e; + } + + // Creates request and a listener with no callback function + ProtoBuf reply = new ProtoBuf(LocserverMessageTypes.GLOC_REPLY); + Request plainRequest = + new PlainRequest(REQUEST_QUERY_LOC, (short)0, payload.toByteArray()); + ProtoRequestListener listener = new ProtoRequestListener(reply, null); + plainRequest.setListener(listener); + + // Immediately send request and block for response until REQUEST_TIMEOUT + MobileServiceMux serviceMux = MobileServiceMux.getSingleton(); + serviceMux.submitRequest(plainRequest, true); + ProtoBuf response; + try { + response = (ProtoBuf)listener.getAsyncResult().get(REQUEST_TIMEOUT); + } catch (InterruptedException e) { + Log.e(TAG, "forwardGeocode(): response timeout"); + throw new IOException("response time-out"); + } + + if (response == null) { + throw new IOException("Unable to parse response from server"); + } + + // Parse the response + int status1 = response.getInt(GLocReply.STATUS); + if (status1 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "forwardGeocode(): RPC failed with status " + status1); + throw new IOException("RPC failed with status " + status1); + } + + if (response.has(GLocReply.PLATFORM_KEY)) { + String platformKey = response.getString(GLocReply.PLATFORM_KEY); + if (!TextUtils.isEmpty(platformKey)) { + setPlatformKey(platformKey); + } + } + + if (!response.has(GLocReply.REPLY_ELEMENTS)) { + Log.e(TAG, "forwardGeocode(): no ReplyElement"); + return; + } + ProtoBuf replyElement = response.getProtoBuf(GLocReply.REPLY_ELEMENTS); + + int status2 = replyElement.getInt(GLocReplyElement.STATUS); + if (status2 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.e(TAG, "forwardGeocode(): GLS failed with status " + status2); + return; + } + + if (!replyElement.has(GLocReplyElement.LOCATION)) { + Log.e(TAG, "forwardGeocode(): no location in ReplyElement"); + return; + } + + ProtoBuf location = replyElement.getProtoBuf(GLocReplyElement.LOCATION); + if (!location.has(GLocation.FEATURE)) { + Log.e(TAG, "forwardGeocode(): no feature in GLocation"); + return; + } + + getAddressFromProtoBuf(location, locale, addrs); + } + + /** + * Queues a location collection request to be sent to the server + * + * @param trigger what triggered this collection event + * @param location last known location + * @param cellState cell tower state + * @param scanResults list of wifi points + * @param scanTime real time at which wifi scan happened + * @param immediate true if request should be sent immediately instead of being queued + */ + public synchronized void queueCollectionReport(int trigger, Location location, + CellState cellState, List<ScanResult> scanResults, long scanTime, boolean immediate) { + + // Create a RequestElement + ProtoBuf requestElement = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST_ELEMENT); + + // Include debug profile + if (trigger != -1) { + ProtoBuf debugProfile = new ProtoBuf(GdebugprofileMessageTypes.GDEBUG_PROFILE); + debugProfile.setInt(GDebugProfile.TRIGGER, trigger); + requestElement.setProtoBuf(GLocRequestElement.DEBUG_PROFILE, debugProfile); + + EventLog.writeEvent(COLLECTION_EVENT_LOG_TAG, trigger); + } + + // Include cell profile + if (cellState != null && cellState.isValid()) { + ProtoBuf cellularProfile = new ProtoBuf(GcellularMessageTypes.GCELLULAR_PROFILE); + cellularProfile.setLong(GCellularProfile.TIMESTAMP, cellState.getTime()); + + // Primary cell + ProtoBuf primaryCell = new ProtoBuf(GcellularMessageTypes.GCELL); + primaryCell.setInt(GCell.LAC, cellState.getLac()); + primaryCell.setInt(GCell.CELLID, cellState.getCid()); + if ((cellState.getMcc() != -1) && (cellState.getMnc() != -1)) { + primaryCell.setInt(GCell.MCC, cellState.getMcc()); + primaryCell.setInt(GCell.MNC, cellState.getMnc()); + } + + cellularProfile.setProtoBuf(GCellularProfile.PRIMARY_CELL, primaryCell); + requestElement.setProtoBuf(GLocRequestElement.CELLULAR_PROFILE, cellularProfile); + } + + // Include Wifi profile + if (scanResults != null && scanResults.size() > 0) { + ProtoBuf wifiProfile = new ProtoBuf(GwifiMessageTypes.GWIFI_PROFILE); + wifiProfile.setLong(GWifiProfile.TIMESTAMP, scanTime); + + int count = 0; + for (ScanResult s : scanResults) { + ProtoBuf wifiDevice = new ProtoBuf(GwifiMessageTypes.GWIFI_DEVICE); + wifiDevice.setString(GWifiDevice.MAC, s.BSSID); + wifiDevice.setString(GWifiDevice.SSID, s.SSID); + wifiDevice.setInt(GWifiDevice.RSSI, s.level); + wifiProfile.addProtoBuf(GWifiProfile.WIFI_DEVICES, wifiDevice); + count++; + if (count >= MAX_WIFI_TO_INCLUDE) { + break; + } + } + + requestElement.setProtoBuf(GLocRequestElement.WIFI_PROFILE, wifiProfile); + } + + // Location information + if (location != null) { + ProtoBuf latlngElement = new ProtoBuf(GlatlngMessageTypes.GLAT_LNG); + latlngElement.setInt(GLatLng.LAT_E7, (int)(location.getLatitude() * E7)); + latlngElement.setInt(GLatLng.LNG_E7, (int)(location.getLongitude() * E7)); + + ProtoBuf locationElement = new ProtoBuf(GlocationMessageTypes.GLOCATION); + locationElement.setProtoBuf(GLocation.LAT_LNG, latlngElement); + locationElement.setInt(GLocation.LOC_TYPE, GLocation.LOCTYPE_GPS); + locationElement.setLong(GLocation.TIMESTAMP, location.getTime()); + if (location.hasAccuracy()) { + locationElement.setInt(GLocation.ACCURACY, (int)location.getAccuracy()); + } + if (location.hasSpeed()) { + locationElement.setInt(GLocation.VELOCITY, (int)location.getSpeed()); + } + if (location.hasBearing()) { + locationElement.setInt(GLocation.HEADING, (int)location.getBearing()); + } + + requestElement.setProtoBuf(GLocRequestElement.LOCATION, locationElement); + } + + if (mCurrentCollectionRequest == null) { + mCurrentCollectionRequest = new ProtoBuf(LocserverMessageTypes.GLOC_REQUEST); + + // Create a Platform Profile + ProtoBuf platformProfile = createPlatformProfile(); + if (cellState != null && cellState.isValid()) { + ProtoBuf cellularPlatform = createCellularPlatformProfile(cellState); + platformProfile.setProtoBuf(GPlatformProfile.CELLULAR_PLATFORM_PROFILE, + cellularPlatform); + } + mCurrentCollectionRequest.setProtoBuf(GLocRequest.PLATFORM_PROFILE, platformProfile); + } + mCurrentCollectionRequest.addProtoBuf(GLocRequest.REQUEST_ELEMENTS, requestElement); + + // Immediately upload collection events if buffer exceeds certain size + if (mCurrentCollectionRequest.getCount(GLocRequest.REQUEST_ELEMENTS) + >= MAX_COLLECTION_BUFFER_SIZE) { + immediate = true; + } + + if (immediate) { + // Request to send over wire + uploadCollectionReport(immediate); + } + } + + /** + * Uploads the collection report either immediately or based on MASF's queueing logic. + * Does not need a reply back + * + * @param immediate true if request should be sent immediately instead of being queued + */ + private synchronized void uploadCollectionReport(boolean immediate) { + // There may be nothing to upload + if (mCurrentCollectionRequest == null || + mCurrentCollectionRequest.getCount(GLocRequest.REQUEST_ELEMENTS) == 0) { + return; + } + + ByteArrayOutputStream payload = new ByteArrayOutputStream(); + try { + mCurrentCollectionRequest.outputTo(payload); + } catch (IOException e) { + Log.e(TAG, "uploadCollectionReport(): unable to write request to payload"); + return; + } + + mLastCollectionUploadTime = SystemClock.elapsedRealtime(); + + // Since this has already been written to the wire, we can clear this request + int count = mCurrentCollectionRequest.getCount(GLocRequest.REQUEST_ELEMENTS); + while (count > 0) { + mCurrentCollectionRequest.remove(GLocRequest.REQUEST_ELEMENTS, count - 1); + count--; + } + + // Creates request and a listener with a call back function + ProtoBuf reply = new ProtoBuf(LocserverMessageTypes.GLOC_REPLY); + Request plainRequest = + new PlainRequest(REQUEST_UPLOAD_LOC, (short)0, payload.toByteArray()); + + ProtoRequestListener listener = new ProtoRequestListener(reply, new ServiceCallback() { + public void onRequestComplete(Object result) { + ProtoBuf response = (ProtoBuf) result; + + if (response == null) { + Log.e(TAG, "uploadCollectionReport(): response is null"); + return; + } + + int status1 = response.getInt(GLocReply.STATUS); + if (status1 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.w(TAG, "uploadCollectionReport(): RPC failed with status " + status1); + return; + } + + if (response.has(GLocReply.PLATFORM_KEY)) { + String platformKey = response.getString(GLocReply.PLATFORM_KEY); + if (!TextUtils.isEmpty(platformKey)) { + setPlatformKey(platformKey); + } + } + + if (!response.has(GLocReply.REPLY_ELEMENTS)) { + Log.w(TAG, "uploadCollectionReport(): no ReplyElement"); + return; + } + + int count = response.getCount(GLocReply.REPLY_ELEMENTS); + for (int i = 0; i < count; i++) { + ProtoBuf replyElement = response.getProtoBuf(GLocReply.REPLY_ELEMENTS, i); + int status2 = replyElement.getInt(GLocReplyElement.STATUS); + if (status2 != ResponseCodes.STATUS_STATUS_SUCCESS) { + Log.w(TAG, "uploadCollectionReport(): GLS failed with " + status2); + } + } + + } + }); + plainRequest.setListener(listener); + + // Send request + MobileServiceMux serviceMux = MobileServiceMux.getSingleton(); + serviceMux.submitRequest(plainRequest, immediate); + + } + + private String getPlatformKey() { + if (mPlatformKey != null) { + return mPlatformKey; + } + + try { + File file = new File(LocationManager.SYSTEM_DIR, PLATFORM_KEY_FILE); + FileInputStream istream = new FileInputStream(file); + DataInputStream dataInput = new DataInputStream(istream); + String platformKey = dataInput.readUTF(); + dataInput.close(); + mPlatformKey = platformKey; + return mPlatformKey; + } catch(FileNotFoundException e) { + // No file, just ignore + return null; + } catch(IOException e) { + // Unable to read from file, just ignore + return null; + } + } + + private void setPlatformKey(String platformKey) { + File systemDir = new File(LocationManager.SYSTEM_DIR); + if (!systemDir.exists()) { + if (!systemDir.mkdirs()) { + Log.w(TAG, "setPlatformKey(): couldn't create directory"); + return; + } + } + + try { + File file = new File(LocationManager.SYSTEM_DIR, PLATFORM_KEY_FILE); + FileOutputStream ostream = new FileOutputStream(file); + DataOutputStream dataOut = new DataOutputStream(ostream); + dataOut.writeUTF(platformKey); + dataOut.close(); + mPlatformKey = platformKey; + } catch (FileNotFoundException e) { + Log.w(TAG, "setPlatformKey(): unable to create platform key file"); + } catch (IOException e) { + Log.w(TAG, "setPlatformKey(): unable to write to platform key"); + } + } + + private ProtoBuf createPlatformProfile() { + Locale locale = Locale.getDefault(); + return createPlatformProfile(locale); + } + + private ProtoBuf createPlatformProfile(Locale locale) { + if (mPlatformProfile == null) { + mPlatformProfile = new ProtoBuf(GlocationMessageTypes.GPLATFORM_PROFILE); + mPlatformProfile.setString(GPlatformProfile.VERSION, APPLICATION_VERSION); + mPlatformProfile.setString(GPlatformProfile.PLATFORM, PLATFORM_BUILD); + } + + // Add Locale + if ((locale != null) && (locale.toString() != null)) { + mPlatformProfile.setString(GPlatformProfile.LOCALE, locale.toString()); + } + + // Add Platform Key + String platformKey = getPlatformKey(); + if (!TextUtils.isEmpty(platformKey)) { + mPlatformProfile.setString(GPlatformProfile.PLATFORM_KEY, platformKey); + } + + // Clear out cellular platform profile + mPlatformProfile.setProtoBuf(GPlatformProfile.CELLULAR_PLATFORM_PROFILE, null); + + return mPlatformProfile; + } + + private ProtoBuf createCellularPlatformProfile(CellState cellState) { + if (mCellularPlatformProfile == null) { + // Radio type + int radioType = -1; + if (cellState.getRadioType() == CellState.RADIO_TYPE_GPRS) { + radioType = GCellularPlatformProfile.RADIO_TYPE_GPRS; + } else if (cellState.getRadioType() == CellState.RADIO_TYPE_CDMA) { + radioType = GCellularPlatformProfile.RADIO_TYPE_CDMA; + } else if (cellState.getRadioType() == CellState.RADIO_TYPE_WCDMA) { + radioType = GCellularPlatformProfile.RADIO_TYPE_WCDMA; + } + + // Cellular platform profile + ProtoBuf cellularPlatform = + new ProtoBuf(GlocationMessageTypes.GCELLULAR_PLATFORM_PROFILE); + cellularPlatform.setInt(GCellularPlatformProfile.RADIO_TYPE, radioType); + if ((cellState.getHomeMcc() != -1) && (cellState.getHomeMnc() != -1)) { + cellularPlatform.setInt(GCellularPlatformProfile.HOME_MCC, cellState.getHomeMcc()); + cellularPlatform.setInt(GCellularPlatformProfile.HOME_MNC, cellState.getHomeMnc()); + } + if (cellState.getCarrier() != null) { + cellularPlatform.setString(GCellularPlatformProfile.CARRIER, + cellState.getCarrier()); + } + mCellularPlatformProfile = cellularPlatform; + } + + return mCellularPlatformProfile; + } + + private void getAddressFromProtoBuf(ProtoBuf location, Locale locale, List<Address> addrs) { + + double lat = -1; + double lng = -1; + + if (location.has(GLocation.LAT_LNG)) { + ProtoBuf latlng = location.getProtoBuf(GLocation.LAT_LNG); + lat = latlng.getInt(GLatLng.LAT_E7)/E7; + lng = latlng.getInt(GLatLng.LNG_E7)/E7; + } + + for (int a = 0; a < location.getCount(GLocation.FEATURE); a++) { + + Address output = new Address(locale); + + ProtoBuf feature = location.getProtoBuf(GLocation.FEATURE, a); + output.setFeatureName(feature.getString(GFeature.NAME)); + + if (feature.has(GFeature.CENTER)) { + ProtoBuf center = feature.getProtoBuf(GFeature.CENTER); + output.setLatitude(center.getInt(GLatLng.LAT_E7)/E7); + output.setLongitude(center.getInt(GLatLng.LNG_E7)/E7); + + } else if (location.has(GLocation.LAT_LNG)) { + output.setLatitude(lat); + output.setLongitude(lng); + } + + ProtoBuf address = feature.getProtoBuf(GFeature.ADDRESS); + + for (int i = 0; i < address.getCount(GAddress.FORMATTED_ADDRESS_LINE); i++) { + String line = address.getString(GAddress.FORMATTED_ADDRESS_LINE, i); + output.setAddressLine(i, line); + } + + for (int i = 0; i < address.getCount(GAddress.COMPONENT); i++) { + ProtoBuf component = address.getProtoBuf(GAddress.COMPONENT, i); + int type = component.getInt(GAddressComponent.FEATURE_TYPE); + String name = component.getString(GAddressComponent.NAME); + + switch(type) { + case GFeature.FEATURE_TYPE_ADMINISTRATIVE_AREA : + output.setAdminArea(name); + break; + + case GFeature.FEATURE_TYPE_SUB_ADMINISTRATIVE_AREA : + output.setSubAdminArea(name); + break; + + case GFeature.FEATURE_TYPE_LOCALITY : + output.setLocality(name); + break; + + case GFeature.FEATURE_TYPE_THOROUGHFARE : + output.setThoroughfare(name); + break; + + case GFeature.FEATURE_TYPE_POST_CODE : + output.setPostalCode(name); + break; + + case GFeature.FEATURE_TYPE_COUNTRY : + output.setCountryName(name); + break; + + case GFeature.FEATURE_TYPE_COUNTRY_CODE : + output.setCountryCode(name); + break; + + default : + if (android.util.Config.LOGD) { + Log.d(TAG, "getAddressFromProtoBuf(): Ignore feature " + type + "," + name); + } + break; + } + } + + addrs.add(output); + } + } + +} diff --git a/location/java/com/android/internal/location/NetworkLocationProvider.java b/location/java/com/android/internal/location/NetworkLocationProvider.java new file mode 100644 index 0000000..7d3fda1 --- /dev/null +++ b/location/java/com/android/internal/location/NetworkLocationProvider.java @@ -0,0 +1,561 @@ +/* + * 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 com.android.internal.location; + +import com.android.internal.location.protocol.GDebugProfile; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +import android.content.Context; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationProviderImpl; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Log; + +/** + * A location provider which gets approximate location from Google's + * database of cell-tower and wi-fi locations. + * + * <p> It is the responsibility of the LocationManagerService to + * notify this class of any changes in the radio information + * by calling {@link #updateCellState} and of data state + * changes by calling {@link #updateNetworkState} + * + * <p> The LocationManagerService must also notify the provider + * of Wifi updates using the {@link #updateWifiScanResults} + * and {@link #updateWifiEnabledState} + * methods. + * + * <p> The provider uses whichever radio is available - Cell + * or WiFi. If neither is available, it does NOT attempt to + * switch them on. + * + * {@hide} + */ +public class NetworkLocationProvider extends LocationProviderImpl { + private static final String TAG = "NetworkLocationProvider"; + + // Wait at least 60 seconds between network queries + private static final int MIN_NETWORK_RETRY_MILLIS = 60000; + + // Max time to wait for radio update + private static final long MAX_TIME_TO_WAIT_FOR_RADIO = 5 * 1000; // 5 seconds + + // State of entire provider + private int mStatus = AVAILABLE; + private long mStatusUpdateTime = 0; + + // Network state + private int mNetworkState = TEMPORARILY_UNAVAILABLE; + + // Cell state + private static final int MAX_CELL_HISTORY_TO_KEEP = 4; + private LinkedList<CellState> mCellHistory = new LinkedList<CellState>(); + private CellState mCellState = null; + private long mLastCellStateChangeTime = 0; + private long mLastCellLockTime = 0; + + // Wifi state + private static final long MIN_TIME_BETWEEN_WIFI_REPORTS = 45 * 1000; // 45 seconds + private List<ScanResult> mWifiLastScanResults = null; + private long mLastWifiScanTriggerTime = 0; + private long mLastWifiScanElapsedTime = 0; + private long mLastWifiScanRealTime = 0; + private long mWifiScanFrequency = MIN_TIME_BETWEEN_WIFI_REPORTS; + private boolean mWifiEnabled = false; + + // Last known location state + private Location mLocation = new Location(LocationManager.NETWORK_PROVIDER); + private long mLastNetworkQueryTime = 0; // Last network request, successful or not + private long mLastSuccessfulNetworkQueryTime = 0; // Last successful network query time + + // Is provider enabled by user -- ignored by this class + private boolean mEnabled; + + // Is provider being used by an application + private HashSet<String> mApplications = new HashSet<String>(); + private boolean mTracking = false; + + // Location masf service + private LocationMasfClient mMasfClient; + + // Context of location manager service + private Context mContext; + + public static boolean isSupported() { + // This class provides a Google-specific location feature, so it's enabled only + // when the system property ro.com.google.enable_google_location_features is set. + if (!SystemProperties.get("ro.com.google.enable_google_location_features").equals("1")) { + return false; + } + + // Otherwise, assume cell location should work if we are not running in the emulator + return !SystemProperties.get("ro.kernel.qemu").equals("1"); + } + + public NetworkLocationProvider(Context context, LocationMasfClient masfClient) { + super(LocationManager.NETWORK_PROVIDER); + mContext = context; + mMasfClient = masfClient; + } + + @Override + public void updateNetworkState(int state) { + if (state == mNetworkState) { + return; + } + log("updateNetworkState(): Updating network state to " + state); + mNetworkState = state; + + updateStatus(mNetworkState); + } + + @Override + public void updateCellState(CellState newState) { + if (newState == null) { + log("updateCellState(): Cell state is invalid"); + return; + } + + if (mCellState != null && mCellState.equals(newState)) { + log("updateCellState(): Cell state is the same"); + return; + } + + // Add previous state to history + if ((mCellState != null) && mCellState.isValid()) { + if (mCellHistory.size() >= MAX_CELL_HISTORY_TO_KEEP) { + mCellHistory.remove(0); + } + mCellHistory.add(mCellState); + } + + mCellState = newState; + log("updateCellState(): Received"); + + mLastCellLockTime = 0; + mLastCellStateChangeTime = SystemClock.elapsedRealtime(); + } + + public void updateCellLockStatus(boolean acquired) { + if (acquired) { + mLastCellLockTime = SystemClock.elapsedRealtime(); + } else { + mLastCellLockTime = 0; + } + } + + @Override + public boolean requiresNetwork() { + return true; + } + + @Override + public boolean requiresSatellite() { + return false; + } + + @Override + public boolean requiresCell() { + return true; + } + + @Override + public boolean hasMonetaryCost() { + return true; + } + + @Override + public boolean supportsAltitude() { + return false; + } + + @Override + public boolean supportsSpeed() { + return false; + } + + @Override + public boolean supportsBearing() { + return false; + } + + @Override + public int getPowerRequirement() { + return Criteria.POWER_LOW; + } + + @Override + public void enable() { + // Nothing else needs to be done + mEnabled = true; + } + + @Override + public void disable() { + // Nothing else needs to be done + mEnabled = false; + } + + @Override + public boolean isEnabled() { + return mEnabled; + } + + @Override + public int getAccuracy() { + return Criteria.ACCURACY_COARSE; + } + + @Override + public int getStatus(Bundle extras) { + return mStatus; + } + + @Override + public long getStatusUpdateTime() { + return mStatusUpdateTime; + } + + @Override + public void setMinTime(long minTime) { + if (minTime < MIN_TIME_BETWEEN_WIFI_REPORTS) { + mWifiScanFrequency = MIN_TIME_BETWEEN_WIFI_REPORTS; + } else { + mWifiScanFrequency = minTime; + } + super.setMinTime(minTime); + } + + @Override + public boolean getLocation(Location l) { + + long now = SystemClock.elapsedRealtime(); + + // Trigger a wifi scan and wait for its results if necessary + if ((mWifiEnabled) && + (mWifiLastScanResults == null || + ((now - mLastWifiScanElapsedTime) > mWifiScanFrequency))) { + + boolean fallback = false; + + // If scan has been recently triggered + if (mLastWifiScanTriggerTime != 0 && + ((now - mLastWifiScanTriggerTime) < mWifiScanFrequency)) { + if ((now - mLastWifiScanTriggerTime) > MAX_TIME_TO_WAIT_FOR_RADIO) { + // If no results from last trigger available, use cell results + // This will also trigger a new scan + log("getLocation(): falling back to cell"); + fallback = true; + } else { + // Just wait for the Wifi results to be available + return false; + } + } + + WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + log("getLocation(): triggering a wifi scan"); + mLastWifiScanTriggerTime = now; + boolean succeeded = wifiManager.startScan(); + if (!succeeded) { + log("getLocation(): wifi scan did not succeed"); + // Wifi trigger failed, use cell results + fallback = true; + } + + // Wait for scan results + if (!fallback) { + return false; + } + } + + // If waiting for cell location + if (mLastCellLockTime != 0 && ((now - mLastCellLockTime) < MAX_TIME_TO_WAIT_FOR_RADIO)) { + return false; + } + + // Update Location + // 1) If there has been a cell state change + // 2) If there was no successful reply for last network request + if (mLastCellStateChangeTime > mLastNetworkQueryTime) { + updateLocation(); + return false; + + } else if ((mLastNetworkQueryTime != 0) + && (mLastNetworkQueryTime > mLastSuccessfulNetworkQueryTime) + && ((now - mLastNetworkQueryTime) > MIN_NETWORK_RETRY_MILLIS)) { + updateLocation(); + return false; + } + + if (mLocation != null && mLocation.getAccuracy() > 0) { + + // We could have a Cell Id location which hasn't changed in a + // while because we haven't switched towers so if the last location + // time + mWifiScanFrequency is less than current time update the + // locations time. + long currentTime = System.currentTimeMillis(); + if ((mLocation.getTime() + mWifiScanFrequency) < currentTime) { + mLocation.setTime(currentTime); + } + l.set(mLocation); + return true; + } else { + return false; + } + } + + @Override + public void enableLocationTracking(boolean enable) { + if (enable == mTracking) { + return; + } + + log("enableLocationTracking(): " + enable); + mTracking = enable; + + if (!enable) { + // When disabling the location provider, be sure to clear out old location + clearLocation(); + } else { + // When enabling provider, force location + forceLocation(); + } + } + + @Override + public boolean isLocationTracking() { + return mTracking; + } + + /** + * Notifies the provider that there are scan results available. + * + * @param scanResults list of wifi scan results + */ + public void updateWifiScanResults(List<ScanResult> scanResults) { + if (!mTracking) { + return; + } + + long now = SystemClock.elapsedRealtime(); + + if (scanResults == null) { + mWifiLastScanResults = null; + mLastWifiScanElapsedTime = now; + mLastWifiScanRealTime = System.currentTimeMillis(); + + log("updateWifIScanResults(): NULL APs"); + + // Force cell location since no wifi results available + if (mWifiEnabled) { + mLastCellLockTime = 0; + mLastCellStateChangeTime = SystemClock.elapsedRealtime(); + } + + } else if ((mWifiLastScanResults == null) + || (mWifiLastScanResults.size() <= 2 && scanResults.size() > mWifiLastScanResults.size()) + || ((now - mLastWifiScanElapsedTime) > mWifiScanFrequency)) { + + if (mWifiLastScanResults == null) { + mWifiLastScanResults = new ArrayList<ScanResult>(); + } else { + mWifiLastScanResults.clear(); + } + mWifiLastScanResults.addAll(scanResults); + mLastWifiScanElapsedTime = now; + mLastWifiScanRealTime = System.currentTimeMillis(); + + log("updateWifIScanResults(): " + mWifiLastScanResults.size() + " APs"); + updateLocation(); + + } + } + + /** + * Notifies the provider if Wifi has been enabled or disabled + * by the user + * + * @param enabled true if wifi is enabled; false otherwise + */ + public void updateWifiEnabledState(boolean enabled) { + mWifiEnabled = enabled; + + log("updateWifiEnabledState(): " + enabled); + + // Force location update + forceLocation(); + } + + public void addListener(String[] applications) { + if (applications != null) { + for (String app : applications) { + String a = app.replaceAll("com.google.android.", ""); + a = a.replaceAll("com.android.", ""); + mApplications.add(a); + log("addListener(): " + a); + } + } + } + + public void removeListener(String[] applications) { + if (applications != null) { + for (String app : applications) { + String a = app.replaceAll("com.google.android.", ""); + a = a.replaceAll("com.android.", ""); + mApplications.remove(a); + log("removeListener(): " + a); + } + } + } + + private void clearLocation() { + mLocation.setAccuracy(-1); + updateStatus(TEMPORARILY_UNAVAILABLE); + } + + private void forceLocation() { + if (mWifiEnabled) { + // Force another wifi scan + mWifiLastScanResults = null; + mLastWifiScanTriggerTime = 0; + mLastWifiScanElapsedTime = 0; + mLastWifiScanRealTime = 0; + } else { + // Force another cell location request + mLastCellLockTime = 0; + mLastCellStateChangeTime = SystemClock.elapsedRealtime(); + } + } + + private void updateStatus(int status) { + if (status != mStatus) { + mStatus = status; + mStatusUpdateTime = SystemClock.elapsedRealtime(); + } + } + + /** + * Gets location from the server is applications are tracking this provider + * + */ + private void updateLocation() { + + // If not being tracked, no need to do anything. + if (!mTracking) { + return; + } + + // If network is not available, can't do anything + if (mNetworkState != AVAILABLE) { + return; + } + + final long now = SystemClock.elapsedRealtime(); + + // There is a pending network request + if ((mLastNetworkQueryTime != 0) && + (mLastNetworkQueryTime > mLastSuccessfulNetworkQueryTime) && + ((now - mLastNetworkQueryTime) <= MIN_NETWORK_RETRY_MILLIS)) { + return; + } + + // Don't include wifi points if they're too old + List<ScanResult> scanResults = null; + if (mWifiEnabled && (mWifiLastScanResults != null && + ((now - mLastWifiScanElapsedTime) < (mWifiScanFrequency + MAX_TIME_TO_WAIT_FOR_RADIO)))) { + scanResults = mWifiLastScanResults; + } + + // If no valid cell information available + boolean noCell = mCellState == null || !mCellState.isValid(); + + // If no valid wifi information available + boolean noWifi = scanResults == null || (scanResults.size() == 0); + + // If no cell-id or wi-fi update, just return invalid location + if (noCell && noWifi) { + clearLocation(); + return; + } + + // What kind of a network location request was it + int trigger; + if (!mWifiEnabled) { + if (!noCell) { + trigger = GDebugProfile.TRIGGER_CELL_AND_WIFI_CHANGE; + } else { + trigger = GDebugProfile.TRIGGER_WIFI_CHANGE; + } + } else { + trigger = GDebugProfile.TRIGGER_CELL_CHANGE; + } + + try { + mLastNetworkQueryTime = now; + mMasfClient.getNetworkLocation(mApplications, trigger, mCellState, mCellHistory, + scanResults, mLastWifiScanRealTime, new Callback() { + public void locationReceived(Location location, boolean networkSuccessful) { + // If location is valid and not the same as previously known location + if ((location != null) && (location.getAccuracy() > 0) && + (location.getTime() != mLocation.getTime())) { + mLocation.set(location); + updateStatus(AVAILABLE); + } else { + // Location is unavailable + clearLocation(); + } + + // Even if no location is available, network request could have succeeded + if (networkSuccessful) { + mLastSuccessfulNetworkQueryTime = SystemClock.elapsedRealtime(); + } + + } + }); + } catch(Exception e) { + Log.e(TAG, "updateLocation got exception:", e); + } + } + + public interface Callback { + + /** + * Callback function to notify of a received network location + * + * @param location location object that is received. may be null if not a valid location + * @param successful true if network query was successful, even if no location was found + */ + void locationReceived(Location location, boolean successful); + } + + private void log(String log) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, log); + } + } + +} diff --git a/location/java/com/android/internal/location/NmeaParser.java b/location/java/com/android/internal/location/NmeaParser.java new file mode 100644 index 0000000..43afa1d --- /dev/null +++ b/location/java/com/android/internal/location/NmeaParser.java @@ -0,0 +1,437 @@ +/* + * 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 com.android.internal.location; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import android.location.Location; +import android.os.Bundle; +import android.util.Log; + +/** + * {@hide} + */ +public class NmeaParser { + + private static final String TAG = "NmeaParser"; + + private static final TimeZone sUtcTimeZone = TimeZone.getTimeZone("UTC"); + + private static final float KNOTS_TO_METERS_PER_SECOND = 0.51444444444f; + + private final String mName; + + private int mYear = -1; + private int mMonth; + private int mDay; + + private long mTime = -1; + private long mBaseTime; + private double mLatitude; + private double mLongitude; + + private boolean mHasAltitude; + private double mAltitude; + private boolean mHasBearing; + private float mBearing; + private boolean mHasSpeed; + private float mSpeed; + + private boolean mNewWaypoint = false; + private Location mLocation = null; + private Bundle mExtras; + + public NmeaParser(String name) { + mName = name; + } + + private boolean updateTime(String time) { + if (time.length() < 6) { + return false; + } + if (mYear == -1) { + // Since we haven't seen a day/month/year yet, + // we can't construct a meaningful time stamp. + // Clean up any old data. + mLatitude = 0.0; + mLongitude = 0.0; + mHasAltitude = false; + mHasBearing = false; + mHasSpeed = false; + mExtras = null; + return false; + } + + int hour, minute; + float second; + try { + hour = Integer.parseInt(time.substring(0, 2)); + minute = Integer.parseInt(time.substring(2, 4)); + second = Float.parseFloat(time.substring(4, time.length())); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Error parsing timestamp " + time); + return false; + } + + int isecond = (int) second; + int millis = (int) ((second - isecond) * 1000); + Calendar c = new GregorianCalendar(sUtcTimeZone); + c.set(mYear, mMonth, mDay, hour, minute, isecond); + long newTime = c.getTimeInMillis() + millis; + + if (mTime == -1) { + mTime = 0; + mBaseTime = newTime; + } + newTime -= mBaseTime; + + // If the timestamp has advanced, copy the temporary data + // into a new Location + if (newTime != mTime) { + mNewWaypoint = true; + mLocation = new Location(mName); + mLocation.setTime(mTime); + mLocation.setLatitude(mLatitude); + mLocation.setLongitude(mLongitude); + if (mHasAltitude) { + mLocation.setAltitude(mAltitude); + } + if (mHasBearing) { + mLocation.setBearing(mBearing); + } + if (mHasSpeed) { + mLocation.setSpeed(mSpeed); + } + mLocation.setExtras(mExtras); + mExtras = null; + + mTime = newTime; + mHasAltitude = false; + mHasBearing = false; + mHasSpeed = false; + } + return true; + } + + private boolean updateDate(String date) { + if (date.length() != 6) { + return false; + } + int month, day, year; + try { + day = Integer.parseInt(date.substring(0, 2)); + month = Integer.parseInt(date.substring(2, 4)); + year = 2000 + Integer.parseInt(date.substring(4, 6)); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Error parsing date " + date); + return false; + } + + mYear = year; + mMonth = month; + mDay = day; + return true; + } + + private boolean updateTime(String time, String date) { + if (!updateDate(date)) { + return false; + } + return updateTime(time); + } + + private boolean updateIntExtra(String name, String value) { + int val; + try { + val = Integer.parseInt(value); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing int " + name + ": " + value, nfe); + return false; + } + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putInt(name, val); + return true; + } + + private boolean updateFloatExtra(String name, String value) { + float val; + try { + val = Float.parseFloat(value); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing float " + name + ": " + value, nfe); + return false; + } + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putFloat(name, val); + return true; + } + + private boolean updateDoubleExtra(String name, String value) { + double val; + try { + val = Double.parseDouble(value); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing double " + name + ": " + value, nfe); + return false; + } + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putDouble(name, val); + return true; + } + + private double convertFromHHMM(String coord) { + double val = Double.parseDouble(coord); + int degrees = ((int) Math.floor(val)) / 100; + double minutes = val - (degrees * 100); + double dcoord = degrees + minutes / 60.0; + return dcoord; + } + + private boolean updateLatLon(String latitude, String latitudeHemi, + String longitude, String longitudeHemi) { + if (latitude.length() == 0 || longitude.length() == 0) { + return false; + } + + // Lat/long values are expressed as {D}DDMM.MMMM + double lat, lon; + try { + lat = convertFromHHMM(latitude); + if (latitudeHemi.charAt(0) == 'S') { + lat = -lat; + } + } catch (NumberFormatException nfe1) { + Log.e(TAG, "Exception parsing lat/long: " + nfe1, nfe1); + return false; + } + + try { + lon = convertFromHHMM(longitude); + if (longitudeHemi.charAt(0) == 'W') { + lon = -lon; + } + } catch (NumberFormatException nfe2) { + Log.e(TAG, "Exception parsing lat/long: " + nfe2, nfe2); + return false; + } + + // Only update if both were parsed cleanly + mLatitude = lat; + mLongitude = lon; + return true; + } + + private boolean updateAltitude(String altitude) { + if (altitude.length() == 0) { + return false; + } + double alt; + try { + alt = Double.parseDouble(altitude); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing altitude " + altitude + ": " + nfe, + nfe); + return false; + } + + mHasAltitude = true; + mAltitude = alt; + return true; + } + + private boolean updateBearing(String bearing) { + float brg; + try { + brg = Float.parseFloat(bearing); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing bearing " + bearing + ": " + nfe, + nfe); + return false; + } + + mHasBearing = true; + mBearing = brg; + return true; + } + + private boolean updateSpeed(String speed) { + float spd; + try { + spd = Float.parseFloat(speed) * KNOTS_TO_METERS_PER_SECOND; + } catch (NumberFormatException nfe) { + Log.e(TAG, "Exception parsing speed " + speed + ": " + nfe, nfe); + return false; + } + + mHasSpeed = true; + mSpeed = spd; + return true; + } + + public boolean parseSentence(String s) { + int len = s.length(); + if (len < 9) { + return false; + } + if (s.charAt(len - 3) == '*') { + // String checksum = s.substring(len - 4, len); + s = s.substring(0, len - 3); + } + String[] tokens = s.split(","); + String sentenceId = tokens[0].substring(3, 6); + + int idx = 1; + try { + if (sentenceId.equals("GGA")) { + String time = tokens[idx++]; + String latitude = tokens[idx++]; + String latitudeHemi = tokens[idx++]; + String longitude = tokens[idx++]; + String longitudeHemi = tokens[idx++]; + String fixQuality = tokens[idx++]; + String numSatellites = tokens[idx++]; + String horizontalDilutionOfPrecision = tokens[idx++]; + String altitude = tokens[idx++]; + String altitudeUnits = tokens[idx++]; + String heightOfGeoid = tokens[idx++]; + String heightOfGeoidUnits = tokens[idx++]; + String timeSinceLastDgpsUpdate = tokens[idx++]; + + updateTime(time); + updateLatLon(latitude, latitudeHemi, + longitude, longitudeHemi); + updateAltitude(altitude); + // updateQuality(fixQuality); + updateIntExtra("numSatellites", numSatellites); + updateFloatExtra("hdop", horizontalDilutionOfPrecision); + + if (mNewWaypoint) { + mNewWaypoint = false; + return true; + } + } else if (sentenceId.equals("GSA")) { + // DOP and active satellites + String selectionMode = tokens[idx++]; // m=manual, a=auto 2d/3d + String mode = tokens[idx++]; // 1=no fix, 2=2d, 3=3d + for (int i = 0; i < 12; i++) { + String id = tokens[idx++]; + } + String pdop = tokens[idx++]; + String hdop = tokens[idx++]; + String vdop = tokens[idx++]; + + // TODO - publish satellite ids + updateFloatExtra("pdop", pdop); + updateFloatExtra("hdop", hdop); + updateFloatExtra("vdop", vdop); + } else if (sentenceId.equals("GSV")) { + // Satellites in view + String numMessages = tokens[idx++]; + String messageNum = tokens[idx++]; + String svsInView = tokens[idx++]; + for (int i = 0; i < 4; i++) { + if (idx + 2 < tokens.length) { + String prnNumber = tokens[idx++]; + String elevation = tokens[idx++]; + String azimuth = tokens[idx++]; + if (idx < tokens.length) { + String snr = tokens[idx++]; + } + } + } + // TODO - publish this info + } else if (sentenceId.equals("RMC")) { + // Recommended minimum navigation information + String time = tokens[idx++]; + String fixStatus = tokens[idx++]; + String latitude = tokens[idx++]; + String latitudeHemi = tokens[idx++]; + String longitude = tokens[idx++]; + String longitudeHemi = tokens[idx++]; + String speed = tokens[idx++]; + String bearing = tokens[idx++]; + String utcDate = tokens[idx++]; + String magneticVariation = tokens[idx++]; + String magneticVariationDir = tokens[idx++]; + String mode = tokens[idx++]; + + if (fixStatus.charAt(0) == 'A') { + updateTime(time, utcDate); + updateLatLon(latitude, latitudeHemi, + longitude, longitudeHemi); + updateBearing(bearing); + updateSpeed(speed); + } + + if (mNewWaypoint) { + return true; + } + } else { + Log.e(TAG, "Unknown sentence: " + s); + } + } catch (ArrayIndexOutOfBoundsException e) { + // do nothing - sentence will have no effect + Log.e(TAG, "AIOOBE", e); + + for (int i = 0; i < tokens.length; i++) { + Log.e(TAG, "Got token #" + i + " = " + tokens[i]); + } + } + + return false; + } + +// } else if (sentenceId.equals("GLL")) { +// // Geographics position lat/long +// String latitude = tokens[idx++]; +// String latitudeHemi = tokens[idx++]; +// String longitude = tokens[idx++]; +// String longitudeHemi = tokens[idx++]; +// String time = tokens[idx++]; +// String status = tokens[idx++]; +// String mode = tokens[idx++]; +// String checksum = tokens[idx++]; +// +// if (status.charAt(0) == 'A') { +// updateTime(time); +// updateLatLon(latitude, latitudeHemi, longitude, longitudeHemi); +// } +//} else if (sentenceId.equals("VTG")) { +// String trackMadeGood = tokens[idx++]; +// String t = tokens[idx++]; +// String unused1 = tokens[idx++]; +// String unused2 = tokens[idx++]; +// String groundSpeedKnots = tokens[idx++]; +// String n = tokens[idx++]; +// String groundSpeedKph = tokens[idx++]; +// String k = tokens[idx++]; +// String checksum = tokens[idx++]; +// +// updateSpeed(groundSpeedKph); + + public Location getLocation() { + return mLocation; + } +} diff --git a/location/java/com/android/internal/location/ProtoRequestListener.java b/location/java/com/android/internal/location/ProtoRequestListener.java new file mode 100644 index 0000000..d73cd05 --- /dev/null +++ b/location/java/com/android/internal/location/ProtoRequestListener.java @@ -0,0 +1,61 @@ +// Copyright 2007 The Android Open Source Project + +package com.android.internal.location; + +import com.google.common.io.GoogleHttpConnection; +import com.google.common.io.protocol.ProtoBuf; +import com.google.masf.ServiceCallback; +import com.google.masf.protocol.Request; +import com.google.masf.protocol.Response; +import com.google.masf.services.AsyncResult; + +import java.io.IOException; +import java.io.InputStream; + +import android.util.Log; + +/** + * Listener for protocol buffer requests + * + * {@hide} + */ + +public class ProtoRequestListener implements Request.Listener { + private final static String TAG = "ProtoRequestListener"; + private AsyncResult result; + private ProtoBuf protoResponse; + + /** + * @return the asynchronous result object + */ + public AsyncResult getAsyncResult() { + return result; + } + + /** + * Constructor for a ProtoRequestListener + * + * @param protoResponse ProtoBuf with correct type to fill response with + * @param callback function to call after completed request (may be null) + */ + public ProtoRequestListener(ProtoBuf protoResponse, ServiceCallback callback) { + this.result = new AsyncResult(callback); + this.protoResponse = protoResponse; + } + + public boolean requestComplete(Request request, Response response) + throws IOException { + InputStream is = response.getInputStream(); + if (response.getStatusCode() == GoogleHttpConnection.HTTP_OK) { + protoResponse.parse(is); + result.setResult(protoResponse); + } else { + result.setResult(null); + } + return true; + } + + public void requestException(Request request, Exception exception) { + Log.e(TAG, "requestException()", exception); + } +} diff --git a/location/java/com/android/internal/location/TrackProvider.java b/location/java/com/android/internal/location/TrackProvider.java new file mode 100644 index 0000000..545d7dc --- /dev/null +++ b/location/java/com/android/internal/location/TrackProvider.java @@ -0,0 +1,720 @@ +// Copyright 2007 The Android Open Source Project + +package com.android.internal.location; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import android.location.Criteria; +import android.location.Location; +import android.location.LocationProviderImpl; +import android.os.Bundle; +import android.util.Config; +import android.util.Log; + +/** + * A dummy provider that returns positions interpolated from a sequence + * of caller-supplied waypoints. The waypoints are supplied as a + * String containing one or more numeric quadruples of the form: + * <br> + * <code> + * <time in millis> <latitude> <longitude> <altitude> + * </code> + * + * <p> The waypoints must be supplied in increasing timestamp order. + * + * <p> The time at which the provider is constructed is considered to + * be time 0, and further requests for positions will return a + * position that is linearly interpolated between the waypoints whose + * timestamps are closest to the amount of wall clock time that has + * elapsed since time 0. + * + * <p> Following the time of the last waypoint, the position of that + * waypoint will continue to be returned indefinitely. + * + * {@hide} + */ +public class TrackProvider extends LocationProviderImpl { + static final String LOG_TAG = "TrackProvider"; + + private static final long INTERVAL = 1000L; + + private boolean mEnabled = true; + + private double mLatitude; + private double mLongitude; + private boolean mHasAltitude; + private boolean mHasBearing; + private boolean mHasSpeed; + private double mAltitude; + private float mBearing; + private float mSpeed; + private Bundle mExtras; + + private long mBaseTime; + private long mLastTime = -1L; + private long mTime; + + private long mMinTime; + private long mMaxTime; + + private List<Waypoint> mWaypoints = new ArrayList<Waypoint>(); + private int mWaypointIndex = 0; + + private boolean mRequiresNetwork = false; + private boolean mRequiresSatellite = false; + private boolean mRequiresCell = false; + private boolean mHasMonetaryCost = false; + private boolean mSupportsAltitude = true; + private boolean mSupportsSpeed = true; + private boolean mSupportsBearing = true; + private boolean mRepeat = false; + private int mPowerRequirement = Criteria.POWER_LOW; + private int mAccuracy = Criteria.ACCURACY_COARSE; + + private float mTrackSpeed = 100.0f; // km/hr - default for kml tracks + + private Location mInitialLocation; + + private void close(Reader rdr) { + try { + if (rdr != null) { + rdr.close(); + } + } catch (IOException e) { + Log.w(LOG_TAG, "Exception closing reader", e); + } + } + + public void readTrack(File trackFile) { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(trackFile), 8192); + String s; + + long lastTime = -Long.MAX_VALUE; + while ((s = br.readLine()) != null) { + String[] tokens = s.split("\\s+"); + if (tokens.length != 4 && tokens.length != 6) { + Log.e(LOG_TAG, "Got track \"" + s + + "\", wanted <time> <long> <lat> <alt> [<bearing> <speed>]"); + continue; + } + long time; + double longitude, latitude, altitude; + try { + time = Long.parseLong(tokens[0]); + longitude = Double.parseDouble(tokens[1]); + latitude = Double.parseDouble(tokens[2]); + altitude = Double.parseDouble(tokens[3]); + } catch (NumberFormatException e) { + Log.e(LOG_TAG, "Got track \"" + s + + "\", wanted <time> <long> <lat> <alt> " + + "[<bearing> <speed>]", e); + continue; + } + + Waypoint w = new Waypoint(getName(), time, latitude, longitude, altitude); + if (tokens.length >= 6) { + float bearing, speed; + try { + bearing = Float.parseFloat(tokens[4]); + speed = Float.parseFloat(tokens[5]); + w.setBearing(bearing); + w.setSpeed(speed); + } catch (NumberFormatException e) { + Log.e(LOG_TAG, "Ignoring bearing and speed \"" + + tokens[4] + "\", \"" + tokens[5] + "\"", e); + } + } + + if (mInitialLocation == null) { + mInitialLocation = w.getLocation(); + } + + // Ignore waypoints whose time is less than or equal to 0 or + // the time of the previous waypoint + if (time < 0) { + Log.e(LOG_TAG, "Ignoring waypoint at negative time=" + time); + continue; + } + if (time <= lastTime) { + Log.e(LOG_TAG, "Ignoring waypoint at time=" + time + + " (< " + lastTime + ")"); + continue; + } + + mWaypoints.add(w); + lastTime = time; + } + + setTimes(); + return; + } catch (IOException e) { + Log.e(LOG_TAG, "Exception reading track file", e); + mWaypoints.clear(); + } finally { + close(br); + } + } + + public void readKml(File kmlFile) { + FileReader kmlReader = null; + try { + kmlReader = new FileReader(kmlFile); + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(kmlReader); + + // Concatenate the text of each <coordinates> tag + boolean inCoordinates = false; + StringBuilder sb = new StringBuilder(); + int eventType = xpp.getEventType(); + do { + if (eventType == XmlPullParser.START_DOCUMENT) { + // do nothing + } else if (eventType == XmlPullParser.END_DOCUMENT) { + // do nothing + } else if (eventType == XmlPullParser.START_TAG) { + String startTagName = xpp.getName(); + if (startTagName.equals("coordinates")) { + inCoordinates = true; + } + } else if (eventType == XmlPullParser.END_TAG) { + String endTagName = xpp.getName(); + if (endTagName.equals("coordinates")) { + inCoordinates = false; + } + } else if (eventType == XmlPullParser.TEXT) { + if (inCoordinates) { + sb.append(xpp.getText()); + sb.append(' '); + } + } + eventType = xpp.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + String coordinates = sb.toString(); + + // Parse the "lon,lat,alt" triples and supply times + // for each waypoint based on a constant speed + Location loc = null; + double KM_PER_HOUR = mTrackSpeed; + double KM_PER_METER = 1.0 / 1000.0; + double MILLIS_PER_HOUR = 60.0 * 60.0 * 1000.0; + double MILLIS_PER_METER = + (1.0 / KM_PER_HOUR) * (KM_PER_METER) * (MILLIS_PER_HOUR); + long time = 0L; + + StringTokenizer st = new StringTokenizer(coordinates, ", "); + while (st.hasMoreTokens()) { + try { + String lon = st.nextToken(); + String lat = st.nextToken(); + String alt = st.nextToken(); + if (Config.LOGD) { + Log.d(LOG_TAG, + "lon=" + lon + ", lat=" + lat + ", alt=" + alt); + } + + double nLongitude = Double.parseDouble(lon); + double nLatitude = Double.parseDouble(lat); + double nAltitude = Double.parseDouble(alt); + + Location nLoc = new Location(getName()); + nLoc.setLatitude(nLatitude); + nLoc.setLongitude(nLongitude); + if (loc != null) { + double distance = loc.distanceTo(nLoc); + if (Config.LOGD) { + Log.d(LOG_TAG, "distance = " + distance); + } + time += (long) (distance * MILLIS_PER_METER); + } + + Waypoint w = new Waypoint(getName(), time, + nLatitude, nLongitude, nAltitude); + if (supportsSpeed()) { + w.setSpeed(mTrackSpeed); + } + if (supportsBearing()) { + w.setBearing(0.0f); + } + mWaypoints.add(w); + + if (mInitialLocation == null) { + mInitialLocation = w.getLocation(); + } + + loc = nLoc; + } catch (NumberFormatException nfe) { + Log.e(LOG_TAG, "Got NumberFormatException reading KML data: " + + nfe, nfe); + } + } + + setTimes(); + return; + } catch (IOException ioe) { + mWaypoints.clear(); + Log.e(LOG_TAG, "Exception reading KML data: " + ioe, ioe); + // fall through + } catch (XmlPullParserException xppe) { + mWaypoints.clear(); + Log.e(LOG_TAG, "Exception reading KML data: " + xppe, xppe); + // fall through + } finally { + close(kmlReader); + } + } + + public void readNmea(String name, File file) { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(file), 8192); + String s; + + String provider = getName(); + NmeaParser parser = new NmeaParser(name); + while ((s = br.readLine()) != null) { + boolean newWaypoint = parser.parseSentence(s); + if (newWaypoint) { + Location loc = parser.getLocation(); + Waypoint w = new Waypoint(loc); + mWaypoints.add(w); + // Log.i(TAG, "Got waypoint " + w); + } + } + + setTimes(); + return; + } catch (IOException ioe) { + Log.e(LOG_TAG, "Exception reading NMEA data: " + ioe); + mWaypoints.clear(); + } finally { + close(br); + } + } + + private static boolean booleanVal(String tf) { + return (tf == null) || (tf.equalsIgnoreCase("true")); + } + + private static int intVal(String val) { + try { + return (val == null) ? 0 : Integer.parseInt(val); + } catch (NumberFormatException nfe) { + return 0; + } + } + + private static float floatVal(String val) { + try { + return (val == null) ? 0 : Float.parseFloat(val); + } catch (NumberFormatException nfe) { + return 0.0f; + } + } + + public void readProperties(File propertiesFile) { + BufferedReader br = null; + if (!propertiesFile.exists()) { + return; + } + try { + if (Config.LOGD) { + Log.d(LOG_TAG, "Loading properties file " + + propertiesFile.getPath()); + } + br = new BufferedReader(new FileReader(propertiesFile), 8192); + + String s; + while ((s = br.readLine()) != null) { + StringTokenizer st = new StringTokenizer(s); + String command = null; + String value = null; + if (!st.hasMoreTokens()) { + continue; + } + command = st.nextToken(); + if (st.hasMoreTokens()) { + value = st.nextToken(); + } + + if (command.equalsIgnoreCase("requiresNetwork")) { + setRequiresNetwork(booleanVal(value)); + } else if (command.equalsIgnoreCase("requiresSatellite")) { + setRequiresSatellite(booleanVal(value)); + } else if (command.equalsIgnoreCase("requiresCell")) { + setRequiresCell(booleanVal(value)); + } else if (command.equalsIgnoreCase("hasMonetaryCost")) { + setHasMonetaryCost(booleanVal(value)); + } else if (command.equalsIgnoreCase("supportsAltitude")) { + setSupportsAltitude(booleanVal(value)); + } else if (command.equalsIgnoreCase("supportsBearing")) { + setSupportsBearing(booleanVal(value)); + } else if (command.equalsIgnoreCase("repeat")) { + setRepeat(booleanVal(value)); + } else if (command.equalsIgnoreCase("supportsSpeed")) { + setSupportsSpeed(booleanVal(value)); + } else if (command.equalsIgnoreCase("powerRequirement")) { + setPowerRequirement(intVal(value)); + } else if (command.equalsIgnoreCase("accuracy")) { + setAccuracy(intVal(value)); + } else if (command.equalsIgnoreCase("trackspeed")) { + setTrackSpeed(floatVal(value)); + } else { + Log.e(LOG_TAG, "Unknown command \"" + command + "\""); + } + } + } catch (IOException ioe) { + Log.e(LOG_TAG, "IOException reading properties file " + + propertiesFile.getPath(), ioe); + } finally { + try { + if (br != null) { + br.close(); + } + } catch (IOException e) { + Log.w(LOG_TAG, "IOException closing properties file " + + propertiesFile.getPath(), e); + } + } + } + + public TrackProvider(String name) { + super(name); + setTimes(); + } + + public TrackProvider(String name, File file) { + this(name); + + String filename = file.getName(); + if (filename.endsWith("kml")) { + readKml(file); + } else if (filename.endsWith("nmea")) { + readNmea(getName(), file); + } else if (filename.endsWith("track")) { + readTrack(file); + } else { + Log.e(LOG_TAG, "Can't initialize TrackProvider from file " + + filename + " (not *kml, *nmea, or *track)"); + } + setTimes(); + } + + private void setTimes() { + mBaseTime = System.currentTimeMillis(); + if (mWaypoints.size() >= 2) { + mMinTime = mWaypoints.get(0).getTime(); + mMaxTime = mWaypoints.get(mWaypoints.size() - 1).getTime(); + } else { + mMinTime = mMaxTime = 0; + } + } + + private double interp(double d0, double d1, float frac) { + return d0 + frac * (d1 - d0); + } + + private void update() { + // Don't update the position at all unless INTERVAL milliseconds + // have passed since the last request + long time = System.currentTimeMillis() - mBaseTime; + if (time - mLastTime < INTERVAL) { + return; + } + + List<Waypoint> waypoints = mWaypoints; + if (waypoints == null) { + return; + } + int size = waypoints.size(); + if (size < 2) { + return; + } + + long t = time; + if (t < mMinTime) { + t = mMinTime; + } + if (mRepeat) { + t -= mMinTime; + long deltaT = mMaxTime - mMinTime; + t %= 2 * deltaT; + if (t > deltaT) { + t = 2 * deltaT - t; + } + t += mMinTime; + } else if (t > mMaxTime) { + t = mMaxTime; + } + + // Locate the time interval for the current time + // We slide the window since we don't expect to move + // much between calls + + Waypoint w0 = waypoints.get(mWaypointIndex); + Waypoint w1 = waypoints.get(mWaypointIndex + 1); + + // If the right end of the current interval is too early, + // move forward to the next waypoint + while (t > w1.getTime()) { + w0 = w1; + w1 = waypoints.get(++mWaypointIndex + 1); + } + // If the left end of the current interval is too late, + // move back to the previous waypoint + while (t < w0.getTime()) { + w1 = w0; + w0 = waypoints.get(--mWaypointIndex); + } + + // Now we know that w0.mTime <= t <= w1.mTime + long w0Time = w0.getTime(); + long w1Time = w1.getTime(); + long dt = w1Time - w0Time; + + float frac = (dt == 0) ? 0 : ((float) (t - w0Time) / dt); + mLatitude = interp(w0.getLatitude(), w1.getLatitude(), frac); + mLongitude = interp(w0.getLongitude(), w1.getLongitude(), frac); + mHasAltitude = w0.hasAltitude() && w1.hasAltitude(); + if (mSupportsAltitude && mHasAltitude) { + mAltitude = interp(w0.getAltitude(), w1.getAltitude(), frac); + } + if (mSupportsBearing) { + mHasBearing = frac <= 0.5f ? w0.hasBearing() : w1.hasBearing(); + if (mHasBearing) { + mBearing = frac <= 0.5f ? w0.getBearing() : w1.getBearing(); + } + } + if (mSupportsSpeed) { + mHasSpeed = frac <= 0.5f ? w0.hasSpeed() : w1.hasSpeed(); + if (mHasSpeed) { + mSpeed = frac <= 0.5f ? w0.getSpeed() : w1.getSpeed(); + } + } + mLastTime = time; + mTime = time; + } + + public void setRequiresNetwork(boolean requiresNetwork) { + mRequiresNetwork = requiresNetwork; + } + + @Override public boolean requiresNetwork() { + return mRequiresNetwork; + } + + public void setRequiresSatellite(boolean requiresSatellite) { + mRequiresSatellite = requiresSatellite; + } + + @Override public boolean requiresSatellite() { + return mRequiresSatellite; + } + + public void setRequiresCell(boolean requiresCell) { + mRequiresCell = requiresCell; + } + + @Override public boolean requiresCell() { + return mRequiresCell; + } + + public void setHasMonetaryCost(boolean hasMonetaryCost) { + mHasMonetaryCost = hasMonetaryCost; + } + + @Override public boolean hasMonetaryCost() { + return mHasMonetaryCost; + } + + public void setSupportsAltitude(boolean supportsAltitude) { + mSupportsAltitude = supportsAltitude; + } + + @Override public boolean supportsAltitude() { + return mSupportsAltitude; + } + + public void setSupportsSpeed(boolean supportsSpeed) { + mSupportsSpeed = supportsSpeed; + } + + @Override public boolean supportsSpeed() { + return mSupportsSpeed; + } + + public void setSupportsBearing(boolean supportsBearing) { + mSupportsBearing = supportsBearing; + } + + @Override public boolean supportsBearing() { + return mSupportsBearing; + } + + public void setRepeat(boolean repeat) { + mRepeat = repeat; + } + + public void setPowerRequirement(int powerRequirement) { + if (powerRequirement < Criteria.POWER_LOW || + powerRequirement > Criteria.POWER_HIGH) { + throw new IllegalArgumentException("powerRequirement = " + + powerRequirement); + } + mPowerRequirement = powerRequirement; + } + + @Override public int getPowerRequirement() { + return mPowerRequirement; + } + + public void setAccuracy(int accuracy) { + mAccuracy = accuracy; + } + + @Override public int getAccuracy() { + return mAccuracy; + } + + public void setTrackSpeed(float trackSpeed) { + mTrackSpeed = trackSpeed; + } + + @Override public void enable() { + mEnabled = true; + } + + @Override public void disable() { + mEnabled = false; + } + + @Override public boolean isEnabled() { + return mEnabled; + } + + @Override public int getStatus(Bundle extras) { + return AVAILABLE; + } + + @Override public boolean getLocation(Location l) { + if (mEnabled) { + update(); + l.setProvider(getName()); + l.setTime(mTime + mBaseTime); + l.setLatitude(mLatitude); + l.setLongitude(mLongitude); + if (mSupportsAltitude && mHasAltitude) { + l.setAltitude(mAltitude); + } + if (mSupportsBearing && mHasBearing) { + l.setBearing(mBearing); + } + if (mSupportsSpeed && mHasSpeed) { + l.setSpeed(mSpeed); + } + if (mExtras != null) { + l.setExtras(mExtras); + } + return true; + } else { + return false; + } + } + + public Location getInitialLocation() { + return mInitialLocation; + } +} + +/** + * A simple tuple of (time stamp, latitude, longitude, altitude), plus optional + * extras. + * + * {@hide} + */ +class Waypoint { + public Location mLocation; + + public Waypoint(Location location) { + mLocation = location; + } + + public Waypoint(String providerName, long time, double latitude, double longitude, + double altitude) { + mLocation = new Location(providerName); + mLocation.setTime(time); + mLocation.setLatitude(latitude); + mLocation.setLongitude(longitude); + mLocation.setAltitude(altitude); + } + + public long getTime() { + return mLocation.getTime(); + } + + public double getLatitude() { + return mLocation.getLatitude(); + } + + public double getLongitude() { + return mLocation.getLongitude(); + } + + public boolean hasAltitude() { + return mLocation.hasAltitude(); + } + + public double getAltitude() { + return mLocation.getAltitude(); + } + + public boolean hasBearing() { + return mLocation.hasBearing(); + } + + public void setBearing(float bearing) { + mLocation.setBearing(bearing); + } + + public float getBearing() { + return mLocation.getBearing(); + } + + public boolean hasSpeed() { + return mLocation.hasSpeed(); + } + + public void setSpeed(float speed) { + mLocation.setSpeed(speed); + } + + public float getSpeed() { + return mLocation.getSpeed(); + } + + public Bundle getExtras() { + return mLocation.getExtras(); + } + + public Location getLocation() { + return new Location(mLocation); + } + + @Override public String toString() { + return "Waypoint[mLocation=" + mLocation + "]"; + } +} diff --git a/location/java/com/android/internal/location/protocol/GAddress.java b/location/java/com/android/internal/location/protocol/GAddress.java new file mode 100644 index 0000000..86a3912 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GAddress.java @@ -0,0 +1,23 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GAddress { + static final int FORMATTED_ADDRESS_LINE = 1; + static final int COMPONENT = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GAddressComponent.java b/location/java/com/android/internal/location/protocol/GAddressComponent.java new file mode 100644 index 0000000..a06a23d --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GAddressComponent.java @@ -0,0 +1,23 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GAddressComponent { + static final int NAME = 1; + static final int FEATURE_TYPE = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GAppProfile.java b/location/java/com/android/internal/location/protocol/GAppProfile.java new file mode 100644 index 0000000..e3332eb --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GAppProfile.java @@ -0,0 +1,26 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GAppProfile { + static final int APP_NAME = 1; + static final int APP_KEY = 2; + static final int REQUEST_TYPE = 3; + static final int SEARCH_TYPE = 4; + static final int SEARCH_TERM = 5; +} + diff --git a/location/java/com/android/internal/location/protocol/GCell.java b/location/java/com/android/internal/location/protocol/GCell.java new file mode 100644 index 0000000..21d1c48 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GCell.java @@ -0,0 +1,29 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GCell { + static final int LAC = 1; + static final int CELLID = 2; + static final int MNC = 3; + static final int MCC = 4; + static final int RSSI = 5; + static final int AGE = 6; + static final int TIMING_ADVANCE = 7; + static final int PRIMARY_SCRAMBLING_CODE = 8; +} + diff --git a/location/java/com/android/internal/location/protocol/GCellularPlatformProfile.java b/location/java/com/android/internal/location/protocol/GCellularPlatformProfile.java new file mode 100644 index 0000000..a17da20 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GCellularPlatformProfile.java @@ -0,0 +1,30 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GCellularPlatformProfile { + static final int RADIO_TYPE_GPRS = 3; + static final int RADIO_TYPE_CDMA = 4; + static final int RADIO_TYPE_WCDMA = 5; + + static final int RADIO_TYPE = 1; + static final int CARRIER = 2; + static final int IP = 3; + static final int HOME_MNC = 4; + static final int HOME_MCC = 5; +} + diff --git a/location/java/com/android/internal/location/protocol/GCellularProfile.java b/location/java/com/android/internal/location/protocol/GCellularProfile.java new file mode 100644 index 0000000..8c85bf7 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GCellularProfile.java @@ -0,0 +1,26 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GCellularProfile { + static final int PRIMARY_CELL = 1; + static final int TIMESTAMP = 2; + static final int NEIGHBORS = 3; + static final int HISTORICAL_CELLS = 4; + static final int PREFETCH_MODE = 5; +} + diff --git a/location/java/com/android/internal/location/protocol/GDebugProfile.java b/location/java/com/android/internal/location/protocol/GDebugProfile.java new file mode 100644 index 0000000..e5c9fe6 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GDebugProfile.java @@ -0,0 +1,36 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GDebugProfile { + static final int TRIGGER_CELL_CHANGE = 1; + static final int TRIGGER_WIFI_CHANGE = 2; + static final int TRIGGER_CELL_AND_WIFI_CHANGE = 3; + static final int TRIGGER_GPS_CHANGE = 4; + static final int TRIGGER_OTHER = 5; + static final int TRIGGER_COLLECTION_START_BURST = 6; + static final int TRIGGER_COLLECTION_RESTART_BURST = 7; + static final int TRIGGER_COLLECTION_CONTINUE_BURST = 8; + static final int TRIGGER_COLLECTION_END_BURST = 9; + static final int TRIGGER_COLLECTION_END_BURST_AT_SAME_LOCATION = 10; + static final int TRIGGER_COLLECTION_MOVED_DISTANCE = 11; + + static final int TRIGGER = 1; + static final int ACTUAL_REQUEST = 2; + static final int CACHE_LOCATION = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GDeviceLocation.java b/location/java/com/android/internal/location/protocol/GDeviceLocation.java new file mode 100644 index 0000000..462ab07 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GDeviceLocation.java @@ -0,0 +1,24 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GDeviceLocation { + static final int LOCATION = 1; + static final int CELL = 2; + static final int WIFI_DEVICE = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GFeature.java b/location/java/com/android/internal/location/protocol/GFeature.java new file mode 100644 index 0000000..73fc1b3 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GFeature.java @@ -0,0 +1,38 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GFeature { + static final int NAME = 1; + static final int FEATURE_TYPE = 2; + static final int ADDRESS = 3; + static final int BOUNDS = 4; + static final int CENTER = 5; + + static final int FEATURE_TYPE_UNKNOWN_TYPE = 0; + static final int FEATURE_TYPE_COUNTRY = 1; + static final int FEATURE_TYPE_COUNTRY_CODE = 2; + static final int FEATURE_TYPE_ADMINISTRATIVE_AREA = 3; + static final int FEATURE_TYPE_SUB_ADMINISTRATIVE_AREA = 4; + static final int FEATURE_TYPE_LOCALITY = 5; + static final int FEATURE_TYPE_SUB_LOCALITY = 6; + static final int FEATURE_TYPE_PREMISES = 7; + static final int FEATURE_TYPE_THOROUGHFARE = 8; + static final int FEATURE_TYPE_SUB_THOROUGHFARE = 9; + static final int FEATURE_TYPE_POST_CODE = 10; +} + diff --git a/location/java/com/android/internal/location/protocol/GGeocodeRequest.java b/location/java/com/android/internal/location/protocol/GGeocodeRequest.java new file mode 100644 index 0000000..4d56cc0 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GGeocodeRequest.java @@ -0,0 +1,24 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GGeocodeRequest { + static final int NUM_FEATURE_LIMIT = 1; + static final int INCLUDE_BOUNDING_BOXES = 2; + static final int BOUNDING_BOX = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GGpsProfile.java b/location/java/com/android/internal/location/protocol/GGpsProfile.java new file mode 100644 index 0000000..be69eb0 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GGpsProfile.java @@ -0,0 +1,29 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GGpsProfile { + static final int FIX_QUALITY_INVALID = 0; + static final int FIX_QUALITY_GPS_FIX = 1; + static final int FIX_QUALITY_DGPS_FIX = 2; + + static final int GPS_FIX_TYPE = 1; + static final int PDOP = 2; + static final int HDOP = 3; + static final int VDOP = 4; +} + diff --git a/location/java/com/android/internal/location/protocol/GLatLng.java b/location/java/com/android/internal/location/protocol/GLatLng.java new file mode 100644 index 0000000..90e23df --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLatLng.java @@ -0,0 +1,23 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GLatLng { + static final int LAT_E7 = 1; + static final int LNG_E7 = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocReply.java b/location/java/com/android/internal/location/protocol/GLocReply.java new file mode 100644 index 0000000..7a0504f --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocReply.java @@ -0,0 +1,24 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GLocReply { + static final int STATUS = 1; + static final int REPLY_ELEMENTS = 2; + static final int PLATFORM_KEY = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocReplyElement.java b/location/java/com/android/internal/location/protocol/GLocReplyElement.java new file mode 100644 index 0000000..bc47fcf --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocReplyElement.java @@ -0,0 +1,24 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GLocReplyElement { + static final int STATUS = 1; + static final int LOCATION = 2; + static final int DEVICE_LOCATION = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocRequest.java b/location/java/com/android/internal/location/protocol/GLocRequest.java new file mode 100644 index 0000000..7761c11 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocRequest.java @@ -0,0 +1,26 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GLocRequest { + static final int PLATFORM_PROFILE = 1; + static final int APP_PROFILES = 2; + static final int USER_PROFILE = 3; + static final int REQUEST_ELEMENTS = 4; + static final int MASF_CLIENT_INFO = 257; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocRequestElement.java b/location/java/com/android/internal/location/protocol/GLocRequestElement.java new file mode 100644 index 0000000..d758953 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocRequestElement.java @@ -0,0 +1,26 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GLocRequestElement { + static final int CELLULAR_PROFILE = 1; + static final int WIFI_PROFILE = 2; + static final int LOCATION = 3; + static final int GEOCODE = 4; + static final int DEBUG_PROFILE = 99; +} + diff --git a/location/java/com/android/internal/location/protocol/GLocation.java b/location/java/com/android/internal/location/protocol/GLocation.java new file mode 100644 index 0000000..9a1eb1f --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GLocation.java @@ -0,0 +1,41 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GLocation { + static final int LOCTYPE_GPS = 0; + static final int LOCTYPE_MAPCENTER = 1; + static final int LOCTYPE_CENTROID = 2; + static final int LOCTYPE_TOWER_LOCATION = 3; + + static final int LAT_LNG = 1; + static final int SOURCE = 2; + static final int ACCURACY = 3; + static final int CONFIDENCE = 4; + static final int FEATURE = 5; + static final int TIMESTAMP = 6; + static final int OBSOLETE = 7; + static final int LOC_TYPE = 8; + static final int MISC = 9; + static final int ALTITUDE = 10; + static final int VERTICAL_ACCURACY = 11; + static final int VELOCITY = 12; + static final int HEADING = 13; + static final int GPS_PROFILE = 14; + static final int LOCATION_STRING = 15; +} + diff --git a/location/java/com/android/internal/location/protocol/GPlatformProfile.java b/location/java/com/android/internal/location/protocol/GPlatformProfile.java new file mode 100644 index 0000000..32a1f8f --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GPlatformProfile.java @@ -0,0 +1,28 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GPlatformProfile { + static final int VERSION = 1; + static final int PLATFORM = 2; + static final int PLATFORM_KEY = 3; + static final int DISTRIBUTION_CHANNEL = 4; + static final int LOCALE = 5; + static final int CELLULAR_PLATFORM_PROFILE = 6; + static final int WIFI_PLATFORM_PROFILE = 7; +} + diff --git a/location/java/com/android/internal/location/protocol/GPrefetchMode.java b/location/java/com/android/internal/location/protocol/GPrefetchMode.java new file mode 100644 index 0000000..041b686 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GPrefetchMode.java @@ -0,0 +1,25 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GPrefetchMode { + static final int PREFETCH_MODE_NO_PREFETCH = 0; + static final int PREFETCH_MODE_REQUESTED_NEIGHBORS_ONLY = 1; + static final int PREFETCH_MODE_MORE_NEIGHBORS = 2; + +} + diff --git a/location/java/com/android/internal/location/protocol/GRectangle.java b/location/java/com/android/internal/location/protocol/GRectangle.java new file mode 100644 index 0000000..b8412e6 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GRectangle.java @@ -0,0 +1,23 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GRectangle { + static final int LOWER_LEFT = 1; + static final int UPPER_RIGHT = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GUserProfile.java b/location/java/com/android/internal/location/protocol/GUserProfile.java new file mode 100644 index 0000000..2ce962c --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GUserProfile.java @@ -0,0 +1,23 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GUserProfile { + static final int USER_NAME = 1; + static final int AUTH_TOKEN = 2; +} + diff --git a/location/java/com/android/internal/location/protocol/GWifiDevice.java b/location/java/com/android/internal/location/protocol/GWifiDevice.java new file mode 100644 index 0000000..62bd03a --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GWifiDevice.java @@ -0,0 +1,26 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GWifiDevice { + static final int MAC = 1; + static final int SSID = 2; + static final int CHANNEL = 3; + static final int RSSI = 4; + static final int NOISE = 5; +} + diff --git a/location/java/com/android/internal/location/protocol/GWifiPlatformProfile.java b/location/java/com/android/internal/location/protocol/GWifiPlatformProfile.java new file mode 100644 index 0000000..7f1efcb --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GWifiPlatformProfile.java @@ -0,0 +1,29 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GWifiPlatformProfile { + static final int RADIO_TYPE_WIFI802_11_A = 1; + static final int RADIO_TYPE_WIFI802_11_B = 2; + static final int RADIO_TYPE_WIFI802_11_G = 3; + static final int RADIO_TYPE_WIFI802_11_N = 4; + + static final int SCANNER_MAC = 1; + static final int SCANNER_IP = 2; + static final int RADIO_TYPE = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GWifiProfile.java b/location/java/com/android/internal/location/protocol/GWifiProfile.java new file mode 100644 index 0000000..e731027 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GWifiProfile.java @@ -0,0 +1,24 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface GWifiProfile { + static final int TIMESTAMP = 1; + static final int WIFI_DEVICES = 2; + static final int PREFETCH_MODE = 3; +} + diff --git a/location/java/com/android/internal/location/protocol/GaddressMessageTypes.java b/location/java/com/android/internal/location/protocol/GaddressMessageTypes.java new file mode 100644 index 0000000..7b6ffd0 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GaddressMessageTypes.java @@ -0,0 +1,39 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GaddressMessageTypes { + public static final ProtoBufType GADDRESS = new ProtoBufType(); + public static final ProtoBufType GADDRESS_COMPONENT = new ProtoBufType(); + + static { + GADDRESS + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_DATA, + GAddress.FORMATTED_ADDRESS_LINE, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GAddress.COMPONENT, GADDRESS_COMPONENT); + + GADDRESS_COMPONENT + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_DATA, + GAddressComponent.NAME, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GAddressComponent.FEATURE_TYPE, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GcellularMessageTypes.java b/location/java/com/android/internal/location/protocol/GcellularMessageTypes.java new file mode 100644 index 0000000..37a6d52 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GcellularMessageTypes.java @@ -0,0 +1,57 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GcellularMessageTypes { + public static final ProtoBufType GCELL = new ProtoBufType(); + public static final ProtoBufType GCELLULAR_PROFILE = new ProtoBufType(); + + static { + GCELL + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GCell.LAC, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GCell.CELLID, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.MNC, new Long(-1)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.MCC, new Long(-1)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.RSSI, new Long(-9999)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.AGE, new Long(0)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.TIMING_ADVANCE, new Long(-1)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCell.PRIMARY_SCRAMBLING_CODE, null); + + GCELLULAR_PROFILE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GCellularProfile.PRIMARY_CELL, GCELL) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT64, + GCellularProfile.TIMESTAMP, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GCellularProfile.NEIGHBORS, GCELL) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GCellularProfile.HISTORICAL_CELLS, GCELL) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCellularProfile.PREFETCH_MODE, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GdebugprofileMessageTypes.java b/location/java/com/android/internal/location/protocol/GdebugprofileMessageTypes.java new file mode 100644 index 0000000..faf5b89 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GdebugprofileMessageTypes.java @@ -0,0 +1,35 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBuf; +import com.google.common.io.protocol.ProtoBufType; + +public class GdebugprofileMessageTypes { + public static final ProtoBufType GDEBUG_PROFILE = new ProtoBufType(); + + static { + GDEBUG_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GDebugProfile.TRIGGER, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_BOOL, + GDebugProfile.ACTUAL_REQUEST, ProtoBuf.TRUE) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GDebugProfile.CACHE_LOCATION, GlocationMessageTypes.GDEVICE_LOCATION); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GfeatureMessageTypes.java b/location/java/com/android/internal/location/protocol/GfeatureMessageTypes.java new file mode 100644 index 0000000..24b182a --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GfeatureMessageTypes.java @@ -0,0 +1,39 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GfeatureMessageTypes { + public static final ProtoBufType GFEATURE_TYPE = new ProtoBufType(); + public static final ProtoBufType GFEATURE = new ProtoBufType(); + + static { + GFEATURE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_DATA, + GFeature.NAME, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GFeature.FEATURE_TYPE, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GFeature.ADDRESS, GaddressMessageTypes.GADDRESS) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GFeature.BOUNDS, GrectangleMessageTypes.GRECTANGLE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GFeature.CENTER, GlatlngMessageTypes.GLAT_LNG); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GlatlngMessageTypes.java b/location/java/com/android/internal/location/protocol/GlatlngMessageTypes.java new file mode 100644 index 0000000..b6a9086 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GlatlngMessageTypes.java @@ -0,0 +1,32 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GlatlngMessageTypes { + public static final ProtoBufType GLAT_LNG = new ProtoBufType(); + + static { + GLAT_LNG + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_FIXED32, + GLatLng.LAT_E7, null) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_FIXED32, + GLatLng.LNG_E7, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GlocationMessageTypes.java b/location/java/com/android/internal/location/protocol/GlocationMessageTypes.java new file mode 100644 index 0000000..067d47c --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GlocationMessageTypes.java @@ -0,0 +1,138 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GlocationMessageTypes { + public static final ProtoBufType GGPS_PROFILE = new ProtoBufType(); + public static final ProtoBufType GLOCATION = new ProtoBufType(); + public static final ProtoBufType GDEVICE_LOCATION = new ProtoBufType(); + public static final ProtoBufType GCELLULAR_PLATFORM_PROFILE = new ProtoBufType(); + public static final ProtoBufType GWIFI_PLATFORM_PROFILE = new ProtoBufType(); + public static final ProtoBufType GPREFETCH_MODE = new ProtoBufType(); + public static final ProtoBufType GPLATFORM_PROFILE = new ProtoBufType(); + public static final ProtoBufType GAPP_PROFILE = new ProtoBufType(); + public static final ProtoBufType GUSER_PROFILE = new ProtoBufType(); + + static { + GGPS_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GGpsProfile.GPS_FIX_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GGpsProfile.PDOP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GGpsProfile.HDOP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GGpsProfile.VDOP, null); + + GLOCATION + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocation.LAT_LNG, GlatlngMessageTypes.GLAT_LNG) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GLocation.SOURCE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.ACCURACY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.CONFIDENCE, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocation.FEATURE, GfeatureMessageTypes.GFEATURE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT64, + GLocation.TIMESTAMP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_BOOL, + GLocation.OBSOLETE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.LOC_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GLocation.MISC, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.ALTITUDE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.VERTICAL_ACCURACY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.VELOCITY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GLocation.HEADING, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocation.GPS_PROFILE, GGPS_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GLocation.LOCATION_STRING, null); + + GDEVICE_LOCATION + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GDeviceLocation.LOCATION, GLOCATION) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GDeviceLocation.CELL, GcellularMessageTypes.GCELL) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GDeviceLocation.WIFI_DEVICE, GwifiMessageTypes.GWIFI_DEVICE); + + GCELLULAR_PLATFORM_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCellularPlatformProfile.RADIO_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GCellularPlatformProfile.CARRIER, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GCellularPlatformProfile.IP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCellularPlatformProfile.HOME_MNC, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GCellularPlatformProfile.HOME_MCC, null); + + GWIFI_PLATFORM_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GWifiPlatformProfile.SCANNER_MAC, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GWifiPlatformProfile.SCANNER_IP, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiPlatformProfile.RADIO_TYPE, null); + + GPLATFORM_PROFILE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_DATA, + GPlatformProfile.VERSION, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GPlatformProfile.PLATFORM, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GPlatformProfile.PLATFORM_KEY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GPlatformProfile.DISTRIBUTION_CHANNEL, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GPlatformProfile.LOCALE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GPlatformProfile.CELLULAR_PLATFORM_PROFILE, GCELLULAR_PLATFORM_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GPlatformProfile.WIFI_PLATFORM_PROFILE, GWIFI_PLATFORM_PROFILE); + + GAPP_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GAppProfile.APP_NAME, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GAppProfile.APP_KEY, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GAppProfile.REQUEST_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GAppProfile.SEARCH_TYPE, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GAppProfile.SEARCH_TERM, null); + + GUSER_PROFILE + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GUserProfile.USER_NAME, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GUserProfile.AUTH_TOKEN, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GrectangleMessageTypes.java b/location/java/com/android/internal/location/protocol/GrectangleMessageTypes.java new file mode 100644 index 0000000..aeb0047 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GrectangleMessageTypes.java @@ -0,0 +1,32 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GrectangleMessageTypes { + public static final ProtoBufType GRECTANGLE = new ProtoBufType(); + + static { + GRECTANGLE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GRectangle.LOWER_LEFT, GlatlngMessageTypes.GLAT_LNG) + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GRectangle.UPPER_RIGHT, GlatlngMessageTypes.GLAT_LNG); + + } +} diff --git a/location/java/com/android/internal/location/protocol/GwifiMessageTypes.java b/location/java/com/android/internal/location/protocol/GwifiMessageTypes.java new file mode 100644 index 0000000..cd7119b --- /dev/null +++ b/location/java/com/android/internal/location/protocol/GwifiMessageTypes.java @@ -0,0 +1,47 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBufType; + +public class GwifiMessageTypes { + public static final ProtoBufType GWIFI_DEVICE = new ProtoBufType(); + public static final ProtoBufType GWIFI_PROFILE = new ProtoBufType(); + + static { + GWIFI_DEVICE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_DATA, + GWifiDevice.MAC, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GWifiDevice.SSID, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiDevice.CHANNEL, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiDevice.RSSI, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiDevice.NOISE, null); + + GWIFI_PROFILE + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT64, + GWifiProfile.TIMESTAMP, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GWifiProfile.WIFI_DEVICES, GWIFI_DEVICE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_INT32, + GWifiProfile.PREFETCH_MODE, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/LocserverMessageTypes.java b/location/java/com/android/internal/location/protocol/LocserverMessageTypes.java new file mode 100644 index 0000000..8ffd004 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/LocserverMessageTypes.java @@ -0,0 +1,80 @@ +/* + * 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 com.android.internal.location.protocol; + +import com.google.common.io.protocol.ProtoBuf; +import com.google.common.io.protocol.ProtoBufType; + +public class LocserverMessageTypes { + public static final ProtoBufType RESPONSE_CODES = new ProtoBufType(); + public static final ProtoBufType GLOC_REQUEST_ELEMENT = new ProtoBufType(); + public static final ProtoBufType GLOC_REQUEST = new ProtoBufType(); + public static final ProtoBufType GGEOCODE_REQUEST = new ProtoBufType(); + public static final ProtoBufType GLOC_REPLY_ELEMENT = new ProtoBufType(); + public static final ProtoBufType GLOC_REPLY = new ProtoBufType(); + + static { + GLOC_REQUEST_ELEMENT + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.CELLULAR_PROFILE, GcellularMessageTypes.GCELLULAR_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.WIFI_PROFILE, GwifiMessageTypes.GWIFI_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.LOCATION, GlocationMessageTypes.GLOCATION) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.GEOCODE, GGEOCODE_REQUEST) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequestElement.DEBUG_PROFILE, GdebugprofileMessageTypes.GDEBUG_PROFILE); + + GLOC_REQUEST + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_MESSAGE, + GLocRequest.PLATFORM_PROFILE, GlocationMessageTypes.GPLATFORM_PROFILE) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocRequest.APP_PROFILES, GlocationMessageTypes.GAPP_PROFILE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequest.USER_PROFILE, GlocationMessageTypes.GUSER_PROFILE) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocRequest.REQUEST_ELEMENTS, GLOC_REQUEST_ELEMENT) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocRequest.MASF_CLIENT_INFO, null); + + GGEOCODE_REQUEST + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_FIXED32, + GGeocodeRequest.NUM_FEATURE_LIMIT, new Long(1)) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_BOOL, + GGeocodeRequest.INCLUDE_BOUNDING_BOXES, ProtoBuf.FALSE) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GGeocodeRequest.BOUNDING_BOX, GrectangleMessageTypes.GRECTANGLE); + + GLOC_REPLY_ELEMENT + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GLocReplyElement.STATUS, null) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_MESSAGE, + GLocReplyElement.LOCATION, GlocationMessageTypes.GLOCATION) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocReplyElement.DEVICE_LOCATION, GlocationMessageTypes.GDEVICE_LOCATION); + + GLOC_REPLY + .addElement(ProtoBufType.REQUIRED | ProtoBufType.TYPE_INT32, + GLocReply.STATUS, null) + .addElement(ProtoBufType.REPEATED | ProtoBufType.TYPE_MESSAGE, + GLocReply.REPLY_ELEMENTS, GLOC_REPLY_ELEMENT) + .addElement(ProtoBufType.OPTIONAL | ProtoBufType.TYPE_DATA, + GLocReply.PLATFORM_KEY, null); + + } +} diff --git a/location/java/com/android/internal/location/protocol/ResponseCodes.java b/location/java/com/android/internal/location/protocol/ResponseCodes.java new file mode 100644 index 0000000..2ea9318 --- /dev/null +++ b/location/java/com/android/internal/location/protocol/ResponseCodes.java @@ -0,0 +1,45 @@ +/* + * 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 com.android.internal.location.protocol; + +public interface ResponseCodes { + static final int STATUS_STATUS_SUCCESS = 0; + static final int STATUS_STATUS_FAILED = 1; + static final int STATUS_AUTHORIZATION_REJECTED = 2; + static final int STATUS_NO_SOURCE_EXISTS = 3; + static final int STATUS_SIGNAL_TOO_WEAK = 4; + static final int STATUS_INVALID_REQUEST = 5; + static final int STATUS_INVALID_NUM_REQUESTS = 6; + static final int STATUS_INVALID_USERLOCATION_FORMAT = 7; + static final int STATUS_INVALID_OPERATION_CODE = 8; + static final int STATUS_INVALID_MAC_STRING_FORMAT = 9; + static final int STATUS_INVALID_CELLID_STRING_FORMAT = 10; + static final int STATUS_NON_EXISTENT_AP = 11; + static final int STATUS_NON_EXISTENT_CELLID = 12; + static final int STATUS_STATUS_FAILED_NO_SOURCE = 13; + static final int STATUS_STATUS_FAILED_NO_SAVE = 14; + static final int STATUS_PLATFORM_KEY_EXPIRED = 15; + static final int STATUS_NO_STORE_EXISTS = 16; + static final int STATUS_NO_CELLIDDATA_FOR_UPDATE = 17; + static final int STATUS_NON_SUPPORTED_OPERATION_IN_UPDATE = 18; + static final int STATUS_NON_SUPPORTED_OPERATION = 19; + static final int STATUS_STATUS_FAILED_NO_GEOCODE = 20; + static final int STATUS_BLACKLISTED_IP_CELLID = 100; + static final int STATUS_BLACKLISTED_IP_WIFI = 101; + +} + |