diff options
42 files changed, 4357 insertions, 3123 deletions
@@ -197,8 +197,8 @@ LOCAL_SRC_FILES += \ location/java/android/location/IGpsStatusProvider.aidl \ location/java/android/location/ILocationListener.aidl \ location/java/android/location/ILocationManager.aidl \ - location/java/android/location/ILocationProvider.aidl \ location/java/android/location/INetInitiatedListener.aidl \ + location/java/com/android/internal/location/ILocationProvider.aidl \ media/java/android/media/IAudioService.aidl \ media/java/android/media/IAudioFocusDispatcher.aidl \ media/java/android/media/IAudioRoutesObserver.aidl \ @@ -306,7 +306,11 @@ aidl_files := \ frameworks/base/graphics/java/android/graphics/Rect.aidl \ frameworks/base/graphics/java/android/graphics/Region.aidl \ frameworks/base/location/java/android/location/Criteria.aidl \ + frameworks/base/location/java/android/location/Geofence.aidl \ frameworks/base/location/java/android/location/Location.aidl \ + frameworks/base/location/java/android/location/LocationRequest.aidl \ + frameworks/base/location/java/com/android/internal/location/ProviderProperties.aidl \ + frameworks/base/location/java/com/android/internal/location/ProviderRequest.aidl \ frameworks/base/telephony/java/android/telephony/ServiceState.aidl \ frameworks/base/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl \ diff --git a/api/current.txt b/api/current.txt index c19acfe..e7dcf05 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10451,7 +10451,7 @@ package android.location { field public static final android.os.Parcelable.Creator CREATOR; } - public class Criteria implements android.os.Parcelable { + public deprecated class Criteria implements android.os.Parcelable { ctor public Criteria(); ctor public Criteria(android.location.Criteria); method public int describeContents(); @@ -10497,6 +10497,13 @@ package android.location { method public static boolean isPresent(); } + public final class Geofence implements android.os.Parcelable { + method public static android.location.Geofence createCircle(double, double, float); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public final class GpsSatellite { method public float getAzimuth(); method public float getElevation(); @@ -10534,7 +10541,7 @@ package android.location { method public int describeContents(); method public static void distanceBetween(double, double, double, double, float[]); method public float distanceTo(android.location.Location); - method public void dump(android.util.Printer, java.lang.String); + method public deprecated void dump(android.util.Printer, java.lang.String); method public float getAccuracy(); method public double getAltitude(); method public float getBearing(); @@ -10582,65 +10589,96 @@ package android.location { public class LocationManager { method public boolean addGpsStatusListener(android.location.GpsStatus.Listener); method public boolean addNmeaListener(android.location.GpsStatus.NmeaListener); - method public void addProximityAlert(double, double, float, long, android.app.PendingIntent); - method public void addTestProvider(java.lang.String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int); - method public void clearTestProviderEnabled(java.lang.String); - method public void clearTestProviderLocation(java.lang.String); - method public void clearTestProviderStatus(java.lang.String); - method public java.util.List<java.lang.String> getAllProviders(); - method public java.lang.String getBestProvider(android.location.Criteria, boolean); + method public deprecated void addProximityAlert(double, double, float, long, android.app.PendingIntent); + method public deprecated void addTestProvider(java.lang.String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int); + method public deprecated void clearTestProviderEnabled(java.lang.String); + method public deprecated void clearTestProviderLocation(java.lang.String); + method public deprecated void clearTestProviderStatus(java.lang.String); + method public deprecated java.util.List<java.lang.String> getAllProviders(); + method public deprecated java.lang.String getBestProvider(android.location.Criteria, boolean); method public android.location.GpsStatus getGpsStatus(android.location.GpsStatus); - method public android.location.Location getLastKnownLocation(java.lang.String); - method public android.location.LocationProvider getProvider(java.lang.String); - method public java.util.List<java.lang.String> getProviders(boolean); - method public java.util.List<java.lang.String> getProviders(android.location.Criteria, boolean); - method public boolean isProviderEnabled(java.lang.String); + method public deprecated android.location.Location getLastKnownLocation(java.lang.String); + method public deprecated android.location.Location getLastKnownLocation(android.location.Criteria); + method public android.location.Location getLastLocation(android.location.LocationRequest); + method public deprecated android.location.LocationProvider getProvider(java.lang.String); + method public deprecated java.util.List<java.lang.String> getProviders(boolean); + method public deprecated java.util.List<java.lang.String> getProviders(android.location.Criteria, boolean); + method public deprecated boolean isProviderEnabled(java.lang.String); + method public void removeAllGeofences(android.app.PendingIntent); + method public void removeGeofence(android.location.Geofence, android.app.PendingIntent); method public void removeGpsStatusListener(android.location.GpsStatus.Listener); method public void removeNmeaListener(android.location.GpsStatus.NmeaListener); - method public void removeProximityAlert(android.app.PendingIntent); - method public void removeTestProvider(java.lang.String); + method public deprecated void removeProximityAlert(android.app.PendingIntent); + method public deprecated void removeTestProvider(java.lang.String); method public void removeUpdates(android.location.LocationListener); method public void removeUpdates(android.app.PendingIntent); - method public void requestLocationUpdates(java.lang.String, long, float, android.location.LocationListener); - method public void requestLocationUpdates(java.lang.String, long, float, android.location.LocationListener, android.os.Looper); - method public void requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper); - method public void requestLocationUpdates(java.lang.String, long, float, android.app.PendingIntent); - method public void requestLocationUpdates(long, float, android.location.Criteria, android.app.PendingIntent); - method public void requestSingleUpdate(java.lang.String, android.location.LocationListener, android.os.Looper); - method public void requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper); - method public void requestSingleUpdate(java.lang.String, android.app.PendingIntent); - method public void requestSingleUpdate(android.location.Criteria, android.app.PendingIntent); - method public boolean sendExtraCommand(java.lang.String, java.lang.String, android.os.Bundle); - method public void setTestProviderEnabled(java.lang.String, boolean); - method public void setTestProviderLocation(java.lang.String, android.location.Location); - method public void setTestProviderStatus(java.lang.String, int, android.os.Bundle, long); - field public static final java.lang.String GPS_PROVIDER = "gps"; + method public void requestGeofence(android.location.LocationRequest, android.location.Geofence, android.app.PendingIntent); + method public deprecated void requestLocationUpdates(java.lang.String, long, float, android.location.LocationListener); + method public deprecated void requestLocationUpdates(java.lang.String, long, float, android.location.LocationListener, android.os.Looper); + method public deprecated void requestLocationUpdates(long, float, android.location.Criteria, android.location.LocationListener, android.os.Looper); + method public deprecated void requestLocationUpdates(java.lang.String, long, float, android.app.PendingIntent); + method public deprecated void requestLocationUpdates(long, float, android.location.Criteria, android.app.PendingIntent); + method public void requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper); + method public void requestLocationUpdates(android.location.LocationRequest, android.app.PendingIntent); + method public deprecated void requestSingleUpdate(java.lang.String, android.location.LocationListener, android.os.Looper); + method public deprecated void requestSingleUpdate(android.location.Criteria, android.location.LocationListener, android.os.Looper); + method public deprecated void requestSingleUpdate(java.lang.String, android.app.PendingIntent); + method public deprecated void requestSingleUpdate(android.location.Criteria, android.app.PendingIntent); + method public deprecated boolean sendExtraCommand(java.lang.String, java.lang.String, android.os.Bundle); + method public deprecated void setTestProviderEnabled(java.lang.String, boolean); + method public deprecated void setTestProviderLocation(java.lang.String, android.location.Location); + method public deprecated void setTestProviderStatus(java.lang.String, int, android.os.Bundle, long); + field public static final deprecated java.lang.String GPS_PROVIDER = "gps"; field public static final java.lang.String KEY_LOCATION_CHANGED = "location"; - field public static final java.lang.String KEY_PROVIDER_ENABLED = "providerEnabled"; + field public static final deprecated java.lang.String KEY_PROVIDER_ENABLED = "providerEnabled"; field public static final java.lang.String KEY_PROXIMITY_ENTERING = "entering"; - field public static final java.lang.String KEY_STATUS_CHANGED = "status"; - field public static final java.lang.String NETWORK_PROVIDER = "network"; - field public static final java.lang.String PASSIVE_PROVIDER = "passive"; - field public static final java.lang.String PROVIDERS_CHANGED_ACTION = "android.location.PROVIDERS_CHANGED"; + field public static final deprecated java.lang.String KEY_STATUS_CHANGED = "status"; + field public static final deprecated java.lang.String NETWORK_PROVIDER = "network"; + field public static final deprecated java.lang.String PASSIVE_PROVIDER = "passive"; + field public static final deprecated java.lang.String PROVIDERS_CHANGED_ACTION = "android.location.PROVIDERS_CHANGED"; } - public abstract class LocationProvider { - method public abstract int getAccuracy(); + public deprecated class LocationProvider { + method public int getAccuracy(); method public java.lang.String getName(); - method public abstract int getPowerRequirement(); - method public abstract boolean hasMonetaryCost(); + method public int getPowerRequirement(); + method public boolean hasMonetaryCost(); method public boolean meetsCriteria(android.location.Criteria); - method public abstract boolean requiresCell(); - method public abstract boolean requiresNetwork(); - method public abstract boolean requiresSatellite(); - method public abstract boolean supportsAltitude(); - method public abstract boolean supportsBearing(); - method public abstract boolean supportsSpeed(); + method public boolean requiresCell(); + method public boolean requiresNetwork(); + method public boolean requiresSatellite(); + method public boolean supportsAltitude(); + method public boolean supportsBearing(); + method public boolean supportsSpeed(); field public static final int AVAILABLE = 2; // 0x2 field public static final int OUT_OF_SERVICE = 0; // 0x0 field public static final int TEMPORARILY_UNAVAILABLE = 1; // 0x1 } + public final class LocationRequest implements android.os.Parcelable { + method public static android.location.LocationRequest create(); + method public int describeContents(); + method public long getExpireAt(); + method public long getFastestInterval(); + method public long getInterval(); + method public int getNumUpdates(); + method public int getQuality(); + method public android.location.LocationRequest setExpireAt(long); + method public android.location.LocationRequest setExpireIn(long); + method public android.location.LocationRequest setFastestInterval(long); + method public android.location.LocationRequest setInterval(long); + method public android.location.LocationRequest setNumUpdates(int); + method public android.location.LocationRequest setQuality(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final int ACCURACY_BLOCK = 102; // 0x66 + field public static final int ACCURACY_CITY = 104; // 0x68 + field public static final int ACCURACY_FINE = 100; // 0x64 + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int POWER_HIGH = 203; // 0xcb + field public static final int POWER_LOW = 201; // 0xc9 + field public static final int POWER_NONE = 200; // 0xc8 + } + } package android.media { diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index a4bf43f..6ab323e 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -592,11 +592,30 @@ <!-- True if WallpaperService is enabled --> <bool name="config_enableWallpaperService">true</bool> - <!-- Package name providing network location support. --> - <string name="config_networkLocationProviderPackageName" translatable="false">@null</string> + <!-- Package name(s) containing location provider support. + These packages can contain services implementing location providers, + such as the Geocode Provider, Network Location Provider, and + Fused Location Provider. They will each be searched for + service components implementing these providers. + It is strongly recommended that the packages explicitly named + below are on the system image, so that they will not map to + a 3rd party application. + The location framework also has support for installation + of new location providers at run-time. The new package does not + have to be explicitly listed here, however it must have a signature + that matches the signature of at least one package on this list. + Platforms should overlay additional packages in + config_overlay_locationProviderPackageNames, instead of overlaying + this config, if they only want to append packages and not replace + the entire array. + --> + <string-array name="config_locationProviderPackageNames" translatable="false"> + <item>com.android.location.fused</item> + </string-array> - <!-- Package name providing geocoder API support. --> - <string name="config_geocodeProviderPackageName" translatable="false">@null</string> + <!-- Pacakge name(s) supplied by overlay, and appended to + config_locationProviderPackageNames. --> + <string-array name="config_overlay_locationProviderPackageNames" translatable="false" /> <!-- Boolean indicating if current platform supports bluetooth SCO for off call use cases --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 965d02b..5b86db5 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1415,6 +1415,8 @@ <java-symbol type="array" name="config_serialPorts" /> <java-symbol type="array" name="radioAttributes" /> <java-symbol type="array" name="config_oemUsbModeOverride" /> + <java-symbol type="array" name="config_locationProviderPackageNames" /> + <java-symbol type="array" name="config_overlay_locationProviderPackageNames" /> <java-symbol type="bool" name="config_animateScreenLights" /> <java-symbol type="bool" name="config_automatic_brightness_available" /> <java-symbol type="bool" name="config_sf_limitedAlpha" /> @@ -1493,8 +1495,6 @@ <java-symbol type="string" name="car_mode_disable_notification_title" /> <java-symbol type="string" name="chooser_wallpaper" /> <java-symbol type="string" name="config_datause_iface" /> - <java-symbol type="string" name="config_geocodeProviderPackageName" /> - <java-symbol type="string" name="config_networkLocationProviderPackageName" /> <java-symbol type="string" name="config_wimaxManagerClassname" /> <java-symbol type="string" name="config_wimaxNativeLibLocation" /> <java-symbol type="string" name="config_wimaxServiceClassname" /> diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java index 1f3fb7a..6258a43 100644 --- a/location/java/android/location/Criteria.java +++ b/location/java/android/location/Criteria.java @@ -24,7 +24,9 @@ import android.os.Parcelable; * location provider. Providers maybe ordered according to accuracy, * power usage, ability to report altitude, speed, * and bearing, and monetary cost. + * @deprecated {@link LocationRequest} instead */ +@Deprecated public class Criteria implements Parcelable { /** * A constant indicating that the application does not choose to @@ -326,6 +328,7 @@ public class Criteria implements Parcelable { public static final Parcelable.Creator<Criteria> CREATOR = new Parcelable.Creator<Criteria>() { + @Override public Criteria createFromParcel(Parcel in) { Criteria c = new Criteria(); c.mHorizontalAccuracy = in.readInt(); @@ -340,15 +343,18 @@ public class Criteria implements Parcelable { return c; } + @Override public Criteria[] newArray(int size) { return new Criteria[size]; } }; + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mHorizontalAccuracy); parcel.writeInt(mVerticalAccuracy); @@ -360,4 +366,43 @@ public class Criteria implements Parcelable { parcel.writeInt(mSpeedRequired ? 1 : 0); parcel.writeInt(mCostAllowed ? 1 : 0); } + + private static String powerToString(int power) { + switch (power) { + case NO_REQUIREMENT: + return "NO_REQ"; + case POWER_LOW: + return "LOW"; + case POWER_MEDIUM: + return "MEDIUM"; + case POWER_HIGH: + return "HIGH"; + default: + return "???"; + } + } + + private static String accuracyToString(int accuracy) { + switch (accuracy) { + case NO_REQUIREMENT: + return "---"; + case ACCURACY_HIGH: + return "HIGH"; + case ACCURACY_MEDIUM: + return "MEDIUM"; + case ACCURACY_LOW: + return "LOW"; + default: + return "???"; + } + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Criteria[power=").append(powerToString(mPowerRequirement)); + s.append(" acc=").append(accuracyToString(mHorizontalAccuracy)); + s.append(']'); + return s.toString(); + } } diff --git a/location/java/android/location/Geofence.aidl b/location/java/android/location/Geofence.aidl new file mode 100644 index 0000000..a5c6aa0 --- /dev/null +++ b/location/java/android/location/Geofence.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +parcelable Geofence; diff --git a/location/java/android/location/Geofence.java b/location/java/android/location/Geofence.java new file mode 100644 index 0000000..353a1ca --- /dev/null +++ b/location/java/android/location/Geofence.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a Geofence + */ +public final class Geofence implements Parcelable { + /** @hide */ + public static final int TYPE_HORIZONTAL_CIRCLE = 1; + + private final int mType; + private final double mLatitude; + private final double mLongitude; + private final float mRadius; + + /** + * Create a horizontal, circular geofence. + * @param latitude latitude in degrees + * @param longitude longitude in degrees + * @param radius radius in meters + * @return a new geofence + * @throws IllegalArgumentException if any parameters are out of range + */ + public static Geofence createCircle(double latitude, double longitude, float radius) { + return new Geofence(latitude, longitude, radius); + } + + private Geofence(double latitude, double longitude, float radius) { + checkRadius(radius); + checkLatLong(latitude, longitude); + mType = TYPE_HORIZONTAL_CIRCLE; + mLatitude = latitude; + mLongitude = longitude; + mRadius = radius; + } + + /** @hide */ + public int getType() { + return mType; + } + + /** @hide */ + public double getLatitude() { + return mLatitude; + } + + /** @hide */ + public double getLongitude() { + return mLongitude; + } + + /** @hide */ + public float getRadius() { + return mRadius; + } + + private static void checkRadius(float radius) { + if (radius <= 0) { + throw new IllegalArgumentException("invalid radius: " + radius); + } + } + + private static void checkLatLong(double latitude, double longitude) { + if (latitude > 90.0 || latitude < -90.0) { + throw new IllegalArgumentException("invalid latitude: " + latitude); + } + if (longitude > 180.0 || longitude < -180.0) { + throw new IllegalArgumentException("invalid longitude: " + longitude); + } + } + + private static void checkType(int type) { + if (type != TYPE_HORIZONTAL_CIRCLE) { + throw new IllegalArgumentException("invalid type: " + type); + } + } + + public static final Parcelable.Creator<Geofence> CREATOR = new Parcelable.Creator<Geofence>() { + @Override + public Geofence createFromParcel(Parcel in) { + int type = in.readInt(); + double latitude = in.readDouble(); + double longitude = in.readDouble(); + float radius = in.readFloat(); + checkType(type); + return Geofence.createCircle(latitude, longitude, radius); + } + @Override + public Geofence[] newArray(int size) { + return new Geofence[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mType); + parcel.writeDouble(mLatitude); + parcel.writeDouble(mLongitude); + parcel.writeFloat(mRadius); + } + + private static String typeToString(int type) { + switch (type) { + case TYPE_HORIZONTAL_CIRCLE: + return "CIRCLE"; + default: + checkType(type); + return null; + } + } + + @Override + public String toString() { + return String.format("Geofence[%s %.6f, %.6f %.0fm]", + typeToString(mType), mLatitude, mLongitude, mRadius); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(mLatitude); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(mLongitude); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + Float.floatToIntBits(mRadius); + result = prime * result + mType; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof Geofence)) + return false; + Geofence other = (Geofence) obj; + if (mRadius != other.mRadius) + return false; + if (mLatitude != other.mLatitude) + return false; + if (mLongitude != other.mLongitude) + return false; + if (mType != other.mType) + return false; + return true; + } +} diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 47b7adf..a2ce606 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -20,53 +20,36 @@ import android.app.PendingIntent; import android.location.Address; import android.location.Criteria; import android.location.GeocoderParams; +import android.location.Geofence; import android.location.IGeocodeProvider; import android.location.IGpsStatusListener; import android.location.ILocationListener; import android.location.Location; +import android.location.LocationRequest; import android.os.Bundle; +import com.android.internal.location.ProviderProperties; + /** * System private API for talking with the location service. * - * {@hide} + * @hide */ interface ILocationManager { - List<String> getAllProviders(); - List<String> getProviders(in Criteria criteria, boolean enabledOnly); - String getBestProvider(in Criteria criteria, boolean enabledOnly); - boolean providerMeetsCriteria(String provider, in Criteria criteria); + void requestLocationUpdates(in LocationRequest request, in ILocationListener listener, + in PendingIntent intent, String packageName); + void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName); + + void requestGeofence(in LocationRequest request, in Geofence geofence, + in PendingIntent intent, String packageName); + void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName); - void requestLocationUpdates(String provider, in Criteria criteria, long minTime, float minDistance, - boolean singleShot, in ILocationListener listener); - void requestLocationUpdatesPI(String provider, in Criteria criteria, long minTime, float minDistance, - boolean singleShot, in PendingIntent intent); - void removeUpdates(in ILocationListener listener); - void removeUpdatesPI(in PendingIntent intent); + Location getLastLocation(in LocationRequest request); boolean addGpsStatusListener(IGpsStatusListener listener); void removeGpsStatusListener(IGpsStatusListener listener); - // for reporting callback completion - void locationCallbackFinished(ILocationListener listener); - - boolean sendExtraCommand(String provider, String command, inout Bundle extras); - - void addProximityAlert(double latitude, double longitude, float distance, - long expiration, in PendingIntent intent, in String packageName); - void removeProximityAlert(in PendingIntent intent); - - Bundle getProviderInfo(String provider); - boolean isProviderEnabled(String provider); - - Location getLastKnownLocation(String provider); - - // Used by location providers to tell the location manager when it has a new location. - // Passive is true if the location is coming from the passive provider, in which case - // it need not be shared with other providers. - void reportLocation(in Location location, boolean passive); - boolean geocoderIsPresent(); String getFromLocation(double latitude, double longitude, int maxResults, in GeocoderParams params, out List<Address> addrs); @@ -75,9 +58,17 @@ interface ILocationManager double upperRightLatitude, double upperRightLongitude, int maxResults, in GeocoderParams params, 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); + boolean sendNiResponse(int notifId, int userResponse); + + // --- deprecated --- + List<String> getAllProviders(); + List<String> getProviders(in Criteria criteria, boolean enabledOnly); + String getBestProvider(in Criteria criteria, boolean enabledOnly); + boolean providerMeetsCriteria(String provider, in Criteria criteria); + ProviderProperties getProviderProperties(String provider); + boolean isProviderEnabled(String provider); + + void addTestProvider(String name, in ProviderProperties properties); void removeTestProvider(String provider); void setTestProviderLocation(String provider, in Location loc); void clearTestProviderLocation(String provider); @@ -86,6 +77,17 @@ interface ILocationManager void setTestProviderStatus(String provider, int status, in Bundle extras, long updateTime); void clearTestProviderStatus(String provider); - // for NI support - boolean sendNiResponse(int notifId, int userResponse); + boolean sendExtraCommand(String provider, String command, inout Bundle extras); + + // --- internal --- + + // Used by location providers to tell the location manager when it has a new location. + // Passive is true if the location is coming from the passive provider, in which case + // it need not be shared with other providers. + void reportLocation(in Location location, boolean passive); + + // for reporting callback completion + void locationCallbackFinished(ILocationListener listener); + + } diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java index 5ad60ab..ece4500 100644 --- a/location/java/android/location/Location.java +++ b/location/java/android/location/Location.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.util.Printer; +import android.util.TimeUtils; import java.text.DecimalFormat; import java.util.StringTokenizer; @@ -84,17 +85,6 @@ public class Location implements Parcelable { // Scratchpad private float[] mResults = new float[2]; - public void dump(Printer pw, String prefix) { - pw.println(prefix + "mProvider=" + mProvider + " mTime=" + mTime); - pw.println(prefix + "mElapsedRealtimeNano=" + mElapsedRealtimeNano); - pw.println(prefix + "mLatitude=" + mLatitude + " mLongitude=" + mLongitude); - pw.println(prefix + "mHasAltitude=" + mHasAltitude + " mAltitude=" + mAltitude); - pw.println(prefix + "mHasSpeed=" + mHasSpeed + " mSpeed=" + mSpeed); - pw.println(prefix + "mHasBearing=" + mHasBearing + " mBearing=" + mBearing); - pw.println(prefix + "mHasAccuracy=" + mHasAccuracy + " mAccuracy=" + mAccuracy); - pw.println(prefix + "mExtras=" + mExtras); - } - /** * Constructs a new Location. By default, time, latitude, * longitude, and numSatellites are 0; hasAltitude, hasSpeed, and @@ -766,25 +756,46 @@ public class Location implements Parcelable { mExtras = (extras == null) ? null : new Bundle(extras); } - @Override public String toString() { - return "Location[mProvider=" + mProvider + - ",mTime=" + mTime + - ",mElapsedRealtimeNano=" + mElapsedRealtimeNano + - ",mLatitude=" + mLatitude + - ",mLongitude=" + mLongitude + - ",mHasAltitude=" + mHasAltitude + - ",mAltitude=" + mAltitude + - ",mHasSpeed=" + mHasSpeed + - ",mSpeed=" + mSpeed + - ",mHasBearing=" + mHasBearing + - ",mBearing=" + mBearing + - ",mHasAccuracy=" + mHasAccuracy + - ",mAccuracy=" + mAccuracy + - ",mExtras=" + mExtras + "]"; + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Location["); + s.append(mProvider); + s.append(String.format(" %.6f,%.6f", mLatitude, mLongitude)); + if (mHasAccuracy) s.append(String.format(" acc=%.0f", mAccuracy)); + else s.append(" acc=???"); + if (mTime == 0) { + s.append(" t=?!?"); + } + if (mElapsedRealtimeNano == 0) { + s.append(" et=?!?"); + } else { + long age = SystemClock.elapsedRealtimeNano() - mElapsedRealtimeNano; + s.append(" age="); + TimeUtils.formatDuration(age / 1000000L, s); + } + if (mHasAltitude) s.append(" alt=").append(mAltitude); + if (mHasSpeed) s.append(" vel=").append(mSpeed); + if (mHasBearing) s.append(" bear=").append(mBearing); + + if (mExtras != null) { + s.append(" {").append(mExtras).append('}'); + } + s.append(']'); + return s.toString(); + } + + /** + * @deprecated Use {@link #toString} instead + */ + @Deprecated + public void dump(Printer pw, String prefix) { + pw.println(prefix + toString()); } public static final Parcelable.Creator<Location> CREATOR = new Parcelable.Creator<Location>() { + @Override public Location createFromParcel(Parcel in) { String provider = in.readString(); Location l = new Location(provider); @@ -804,15 +815,18 @@ public class Location implements Parcelable { return l; } + @Override public Location[] newArray(int size) { return new Location[size]; } }; + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mProvider); parcel.writeLong(mTime); @@ -828,5 +842,5 @@ public class Location implements Parcelable { parcel.writeInt(mHasAccuracy ? 1 : 0); parcel.writeFloat(mAccuracy); parcel.writeBundle(mExtras); - } + } } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 15a2928..083b900 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -25,15 +25,15 @@ import android.os.Looper; import android.os.RemoteException; import android.os.Handler; import android.os.Message; -import android.os.SystemClock; import android.util.Log; -import com.android.internal.location.DummyLocationProvider; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import com.android.internal.location.ProviderProperties; + /** * This class provides access to the system location services. These * services allow applications to obtain periodic updates of the @@ -71,7 +71,9 @@ public class LocationManager { * * Requires either of the permissions android.permission.ACCESS_COARSE_LOCATION * or android.permission.ACCESS_FINE_LOCATION. + * @deprecated use the {@link Criteria} class instead */ + @Deprecated public static final String NETWORK_PROVIDER = "network"; /** @@ -87,7 +89,9 @@ public class LocationManager { * <ul> * <li> satellites - the number of satellites used to derive the fix * </ul> + * @deprecated use the {@link Criteria} class instead */ + @Deprecated public static final String GPS_PROVIDER = "gps"; /** @@ -100,10 +104,22 @@ public class LocationManager { * * Requires the permission android.permission.ACCESS_FINE_LOCATION, although if the GPS * is not enabled this provider might only return coarse fixes. + * @deprecated use the {@link Criteria} class instead */ + @Deprecated public static final String PASSIVE_PROVIDER = "passive"; /** + * Name of the Fused location provider.<p> + * This provider combines inputs for all possible location sources + * to provide the best possible Location fix.<p> + * + * Requires the permission android.permission.ACCESS_FINE_LOCATION. + * @hide + */ + public static final String FUSED_PROVIDER = "fused"; + + /** * Key used for the Bundle extra holding a boolean indicating whether * a proximity alert is entering (true) or exiting (false).. */ @@ -112,13 +128,17 @@ public class LocationManager { /** * Key used for a Bundle extra holding an Integer status value * when a status change is broadcast using a PendingIntent. + * @deprecated use the {@link Criteria} class instead */ + @Deprecated public static final String KEY_STATUS_CHANGED = "status"; /** * Key used for a Bundle extra holding an Boolean status value * when a provider enabled/disabled event is broadcast using a PendingIntent. + * @deprecated use the {@link Criteria} class instead */ + @Deprecated public static final String KEY_PROVIDER_ENABLED = "providerEnabled"; /** @@ -141,7 +161,9 @@ public class LocationManager { /** * Broadcast intent action when the configured location providers * change. + * @deprecated use the {@link Criteria} class instead */ + @Deprecated public static final String PROVIDERS_CHANGED_ACTION = "android.location.PROVIDERS_CHANGED"; @@ -274,19 +296,8 @@ public class LocationManager { mContext = context; } - private LocationProvider createProvider(String name, Bundle info) { - DummyLocationProvider provider = - new DummyLocationProvider(name, mService); - 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; + private LocationProvider createProvider(String name, ProviderProperties properties) { + return new LocationProvider(name, properties); } /** @@ -295,15 +306,14 @@ public class LocationManager { * accessed by the calling activity or are currently disabled. * * @return list of Strings containing names of the providers + * @deprecated use the {@link Criteria} class instead */ + @Deprecated public List<String> getAllProviders() { - if (false) { - Log.d(TAG, "getAllProviders"); - } try { return mService.getAllProviders(); - } catch (RemoteException ex) { - Log.e(TAG, "getAllProviders: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } return null; } @@ -315,12 +325,16 @@ public class LocationManager { * @param enabledOnly if true then only the providers which are currently * enabled are returned. * @return list of Strings containing names of the providers + * @deprecated The {@link LocationProvider} class is deprecated. So + * use the {@link Criteria} class to request location instead of + * enumerating providers. */ + @Deprecated public List<String> getProviders(boolean enabledOnly) { try { return mService.getProviders(null, enabledOnly); - } catch (RemoteException ex) { - Log.e(TAG, "getProviders: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } return null; } @@ -332,22 +346,24 @@ public class LocationManager { * @param name the provider name * @return a LocationProvider, or null * - * @throws IllegalArgumentException if name is null + * @throws IllegalArgumentException if name is null or does not exisit * @throws SecurityException if the caller is not permitted to access the * given provider. + * @deprecated The {@link LocationProvider} class is deprecated. So + * use the {@link Criteria} class to request location instead of + * enumerating providers. */ + @Deprecated public LocationProvider getProvider(String name) { - if (name == null) { - throw new IllegalArgumentException("name==null"); - } + checkProvider(name); try { - Bundle info = mService.getProviderInfo(name); - if (info == null) { + ProviderProperties properties = mService.getProviderProperties(name); + if (properties == null) { return null; } - return createProvider(name, info); - } catch (RemoteException ex) { - Log.e(TAG, "getProvider: RemoteException", ex); + return createProvider(name, properties); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } return null; } @@ -361,15 +377,17 @@ public class LocationManager { * @param enabledOnly if true then only the providers which are currently * enabled are returned. * @return list of Strings containing names of the providers + * @deprecated The {@link LocationProvider} class is deprecated. So + * use the {@link Criteria} class to request location instead of + * enumerating providers. */ + @Deprecated public List<String> getProviders(Criteria criteria, boolean enabledOnly) { - if (criteria == null) { - throw new IllegalArgumentException("criteria==null"); - } + checkCriteria(criteria); try { return mService.getProviders(criteria, enabledOnly); - } catch (RemoteException ex) { - Log.e(TAG, "getProviders: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } return null; } @@ -395,15 +413,15 @@ public class LocationManager { * @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 + * @deprecated using an explicit provider doesn't allow fused location */ + @Deprecated public String getBestProvider(Criteria criteria, boolean enabledOnly) { - if (criteria == null) { - throw new IllegalArgumentException("criteria==null"); - } + checkCriteria(criteria); try { return mService.getBestProvider(criteria, enabledOnly); - } catch (RemoteException ex) { - Log.e(TAG, "getBestProvider: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } return null; } @@ -480,16 +498,17 @@ public class LocationManager { * @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. + * @deprecated use the {@link LocationRequest} class instead */ - 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, null, minTime, minDistance, false, listener, null); + @Deprecated + public void requestLocationUpdates(String provider, long minTime, float minDistance, + LocationListener listener) { + checkProvider(provider); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, minTime, minDistance, false); + requestLocationUpdates(request, listener, null, null); } /** @@ -564,17 +583,17 @@ public class LocationManager { * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if listener is null * @throws SecurityException if no suitable permission is present for the provider. + * @deprecated use the {@link LocationRequest} class instead */ - 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"); - } - _requestLocationUpdates(provider, null, minTime, minDistance, false, listener, looper); + @Deprecated + public void requestLocationUpdates(String provider, long minTime, float minDistance, + LocationListener listener, Looper looper) { + checkProvider(provider); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, minTime, minDistance, false); + requestLocationUpdates(request, listener, looper, null); } /** @@ -639,39 +658,17 @@ public class LocationManager { * @throws IllegalArgumentException if criteria is null * @throws IllegalArgumentException if listener is null * @throws SecurityException if no suitable permission is present for the provider. + * @deprecated use the {@link LocationRequest} class instead */ - public void requestLocationUpdates(long minTime, float minDistance, - Criteria criteria, LocationListener listener, Looper looper) { - if (criteria == null) { - throw new IllegalArgumentException("criteria==null"); - } - if (listener == null) { - throw new IllegalArgumentException("listener==null"); - } - _requestLocationUpdates(null, criteria, minTime, minDistance, false, listener, looper); - } - - private void _requestLocationUpdates(String provider, Criteria criteria, long minTime, - float minDistance, boolean singleShot, LocationListener listener, Looper looper) { - if (minTime < 0L) { - minTime = 0L; - } - if (minDistance < 0.0f) { - minDistance = 0.0f; - } + @Deprecated + public void requestLocationUpdates(long minTime, float minDistance, Criteria criteria, + LocationListener listener, Looper looper) { + checkCriteria(criteria); + checkListener(listener); - try { - synchronized (mListeners) { - ListenerTransport transport = mListeners.get(listener); - if (transport == null) { - transport = new ListenerTransport(listener, looper); - } - mListeners.put(listener, transport); - mService.requestLocationUpdates(provider, criteria, minTime, minDistance, singleShot, transport); - } - } catch (RemoteException ex) { - Log.e(TAG, "requestLocationUpdates: DeadObjectException", ex); - } + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, minTime, minDistance, false); + requestLocationUpdates(request, listener, looper, null); } /** @@ -749,16 +746,17 @@ public class LocationManager { * on this device * @throws IllegalArgumentException if intent is null * @throws SecurityException if no suitable permission is present for the provider. + * @deprecated use the {@link LocationRequest} class instead */ - public void requestLocationUpdates(String provider, - long minTime, float minDistance, PendingIntent intent) { - if (provider == null) { - throw new IllegalArgumentException("provider==null"); - } - if (intent == null) { - throw new IllegalArgumentException("intent==null"); - } - _requestLocationUpdates(provider, null, minTime, minDistance, false, intent); + @Deprecated + public void requestLocationUpdates(String provider, long minTime, float minDistance, + PendingIntent intent) { + checkProvider(provider); + checkPendingIntent(intent); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, minTime, minDistance, false); + requestLocationUpdates(request, null, null, intent); } /** @@ -825,32 +823,17 @@ public class LocationManager { * @throws IllegalArgumentException if criteria is null * @throws IllegalArgumentException if intent is null * @throws SecurityException if no suitable permission is present for the provider. + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void requestLocationUpdates(long minTime, float minDistance, Criteria criteria, PendingIntent intent) { - if (criteria == null) { - throw new IllegalArgumentException("criteria==null"); - } - if (intent == null) { - throw new IllegalArgumentException("intent==null"); - } - _requestLocationUpdates(null, criteria, minTime, minDistance, false, intent); - } - - private void _requestLocationUpdates(String provider, Criteria criteria, - long minTime, float minDistance, boolean singleShot, PendingIntent intent) { - if (minTime < 0L) { - minTime = 0L; - } - if (minDistance < 0.0f) { - minDistance = 0.0f; - } + checkCriteria(criteria); + checkPendingIntent(intent); - try { - mService.requestLocationUpdatesPI(provider, criteria, minTime, minDistance, singleShot, intent); - } catch (RemoteException ex) { - Log.e(TAG, "requestLocationUpdates: RemoteException", ex); - } + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, minTime, minDistance, false); + requestLocationUpdates(request, null, null, intent); } /** @@ -879,15 +862,16 @@ public class LocationManager { * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if listener is null * @throws SecurityException if no suitable permission is present for the provider + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void requestSingleUpdate(String provider, LocationListener listener, Looper looper) { - if (provider == null) { - throw new IllegalArgumentException("provider==null"); - } - if (listener == null) { - throw new IllegalArgumentException("listener==null"); - } - _requestLocationUpdates(provider, null, 0L, 0.0f, true, listener, looper); + checkProvider(provider); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, 0, 0, true); + requestLocationUpdates(request, listener, looper, null); } /** @@ -918,15 +902,16 @@ public class LocationManager { * @throws IllegalArgumentException if listener is null * @throws SecurityException if no suitable permission is present to access * the location services + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void requestSingleUpdate(Criteria criteria, LocationListener listener, Looper looper) { - if (criteria == null) { - throw new IllegalArgumentException("criteria==null"); - } - if (listener == null) { - throw new IllegalArgumentException("listener==null"); - } - _requestLocationUpdates(null, criteria, 0L, 0.0f, true, listener, looper); + checkCriteria(criteria); + checkListener(listener); + + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, 0, 0, true); + requestLocationUpdates(request, listener, looper, null); } /** @@ -951,15 +936,16 @@ public class LocationManager { * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if intent is null * @throws SecurityException if no suitable permission is present for the provider + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void requestSingleUpdate(String provider, PendingIntent intent) { - if (provider == null) { - throw new IllegalArgumentException("provider==null"); - } - if (intent == null) { - throw new IllegalArgumentException("intent==null"); - } - _requestLocationUpdates(provider, null, 0L, 0.0f, true, intent); + checkProvider(provider); + checkPendingIntent(intent); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, 0, 0, true); + requestLocationUpdates(request, null, null, intent); } /** @@ -986,15 +972,54 @@ public class LocationManager { * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if intent is null * @throws SecurityException if no suitable permission is present for the provider + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void requestSingleUpdate(Criteria criteria, PendingIntent intent) { - if (criteria == null) { - throw new IllegalArgumentException("criteria==null"); - } - if (intent == null) { - throw new IllegalArgumentException("intent==null"); + checkCriteria(criteria); + checkPendingIntent(intent); + + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, 0, 0, true); + requestLocationUpdates(request, null, null, intent); + } + + public void requestLocationUpdates(LocationRequest request, LocationListener listener, + Looper looper) { + checkListener(listener); + requestLocationUpdates(request, listener, looper, null); + } + + public void requestLocationUpdates(LocationRequest request, PendingIntent intent) { + checkPendingIntent(intent); + requestLocationUpdates(request, null, null, intent); + } + + private ListenerTransport wrapListener(LocationListener listener, Looper looper) { + if (listener == null) return null; + synchronized (mListeners) { + ListenerTransport transport = mListeners.get(listener); + if (transport == null) { + transport = new ListenerTransport(listener, looper); + } + mListeners.put(listener, transport); + return transport; } - _requestLocationUpdates(null, criteria, 0L, 0.0f, true, intent); + } + + private void requestLocationUpdates(LocationRequest request, LocationListener listener, + Looper looper, PendingIntent intent) { + + String packageName = mContext.getPackageName(); + + // wrap the listener class + ListenerTransport transport = wrapListener(listener, looper); + + try { + mService.requestLocationUpdates(request, transport, intent, packageName); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); + } } /** @@ -1006,19 +1031,19 @@ public class LocationManager { * @throws IllegalArgumentException if listener is null */ public void removeUpdates(LocationListener listener) { - if (listener == null) { - throw new IllegalArgumentException("listener==null"); - } - if (false) { - Log.d(TAG, "removeUpdates: listener = " + listener); + checkListener(listener); + String packageName = mContext.getPackageName(); + + ListenerTransport transport; + synchronized (mListeners) { + transport = mListeners.remove(listener); } + if (transport == null) return; + try { - ListenerTransport transport = mListeners.remove(listener); - if (transport != null) { - mService.removeUpdates(transport); - } - } catch (RemoteException ex) { - Log.e(TAG, "removeUpdates: DeadObjectException", ex); + mService.removeUpdates(transport, null, packageName); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1031,16 +1056,13 @@ public class LocationManager { * @throws IllegalArgumentException if intent is null */ public void removeUpdates(PendingIntent intent) { - if (intent == null) { - throw new IllegalArgumentException("intent==null"); - } - if (false) { - Log.d(TAG, "removeUpdates: intent = " + intent); - } + checkPendingIntent(intent); + String packageName = mContext.getPackageName(); + try { - mService.removeUpdatesPI(intent); - } catch (RemoteException ex) { - Log.e(TAG, "removeUpdates: RemoteException", ex); + mService.removeUpdates(null, intent, packageName); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1086,20 +1108,31 @@ public class LocationManager { * * @throws SecurityException if no permission exists for the required * providers. + * @deprecated use the {@link LocationRequest} class instead */ - public void addProximityAlert(double latitude, double longitude, - float radius, long expiration, PendingIntent intent) { - if (false) { - Log.d(TAG, "addProximityAlert: latitude = " + latitude + - ", longitude = " + longitude + ", radius = " + radius + - ", expiration = " + expiration + - ", intent = " + intent); + @Deprecated + public void addProximityAlert(double latitude, double longitude, float radius, long expiration, + PendingIntent intent) { + checkPendingIntent(intent); + if (expiration < 0) expiration = Long.MAX_VALUE; + + Geofence fence = Geofence.createCircle(latitude, longitude, radius); + LocationRequest request = new LocationRequest().setExpireIn(expiration); + try { + mService.requestGeofence(request, fence, intent, mContext.getPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } + } + + public void requestGeofence(LocationRequest request, Geofence fence, PendingIntent intent) { + checkPendingIntent(intent); + checkGeofence(fence); + try { - mService.addProximityAlert(latitude, longitude, radius, expiration, intent, - mContext.getPackageName()); - } catch (RemoteException ex) { - Log.e(TAG, "addProximityAlert: RemoteException", ex); + mService.requestGeofence(request, fence, intent, mContext.getPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1108,15 +1141,40 @@ public class LocationManager { * * @param intent the PendingIntent that no longer needs to be notified of * proximity alerts + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void removeProximityAlert(PendingIntent intent) { - if (false) { - Log.d(TAG, "removeProximityAlert: intent = " + intent); + checkPendingIntent(intent); + String packageName = mContext.getPackageName(); + + try { + mService.removeGeofence(null, intent, packageName); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } + } + + public void removeGeofence(Geofence fence, PendingIntent intent) { + checkPendingIntent(intent); + checkGeofence(fence); + String packageName = mContext.getPackageName(); + try { - mService.removeProximityAlert(intent); - } catch (RemoteException ex) { - Log.e(TAG, "removeProximityAlert: RemoteException", ex); + mService.removeGeofence(fence, intent, packageName); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); + } + } + + public void removeAllGeofences(PendingIntent intent) { + checkPendingIntent(intent); + String packageName = mContext.getPackageName(); + + try { + mService.removeGeofence(null, intent, packageName); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1130,19 +1188,30 @@ public class LocationManager { * * @throws SecurityException if no suitable permission is present for the provider. * @throws IllegalArgumentException if provider is null + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public boolean isProviderEnabled(String provider) { - if (provider == null) { - throw new IllegalArgumentException("provider==null"); - } + checkProvider(provider); + try { return mService.isProviderEnabled(provider); - } catch (RemoteException ex) { - Log.e(TAG, "isProviderEnabled: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); return false; } } + public Location getLastLocation(LocationRequest request) { + try { + return mService.getLastLocation(request); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); + return null; + } + } + + /** * Returns a Location indicating the data from the last known * location fix obtained from the given provider. This can be done @@ -1157,15 +1226,49 @@ public class LocationManager { * * @throws SecurityException if no suitable permission is present for the provider. * @throws IllegalArgumentException if provider is null or doesn't exist + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public Location getLastKnownLocation(String provider) { - if (provider == null) { - throw new IllegalArgumentException("provider==null"); + checkProvider(provider); + + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + provider, 0, 0, true); + + try { + return mService.getLastLocation(request); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); + return null; } + } + + /** + * Return the last know Location that satisfies the given + * criteria. 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 no location is found that satisfies the criteria, null is returned + * + * @param criteria location criteria + * @return the last known location that satisfies criteria, or null + * + * @throws SecurityException if no suitable permission is present + * @throws IllegalArgumentException if criteria is null + * @deprecated use the {@link LocationRequest} class instead + */ + @Deprecated + public Location getLastKnownLocation(Criteria criteria) { + checkCriteria(criteria); + + LocationRequest request = LocationRequest.createFromDeprecatedCriteria( + criteria, 0, 0, true); try { - return mService.getLastKnownLocation(provider); - } catch (RemoteException ex) { - Log.e(TAG, "getLastKnowLocation: RemoteException", ex); + return mService.getLastLocation(request); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); return null; } } @@ -1190,16 +1293,23 @@ public class LocationManager { * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION * Settings.Secure.ALLOW_MOCK_LOCATION} system setting is not enabled * @throws IllegalArgumentException if a provider with the given name already exists + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite, - boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, - boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + ProviderProperties properties = new ProviderProperties(requiresNetwork, + requiresSatellite, requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, + supportsBearing, powerRequirement, accuracy); + if (name.matches(LocationProvider.BAD_CHARS_REGEX)) { + throw new IllegalArgumentException("provider name contains illegal character: " + name); + } + try { - mService.addTestProvider(name, requiresNetwork, requiresSatellite, requiresCell, - hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing, powerRequirement, - accuracy); - } catch (RemoteException ex) { - Log.e(TAG, "addTestProvider: RemoteException", ex); + mService.addTestProvider(name, properties); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1212,12 +1322,14 @@ public class LocationManager { * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled * @throws IllegalArgumentException if no provider with the given name exists + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void removeTestProvider(String provider) { try { mService.removeTestProvider(provider); - } catch (RemoteException ex) { - Log.e(TAG, "removeTestProvider: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1236,23 +1348,27 @@ public class LocationManager { * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled * @throws IllegalArgumentException if no provider with the given name exists * @throws IllegalArgumentException if the location is incomplete + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void setTestProviderLocation(String provider, Location loc) { if (!loc.isComplete()) { + IllegalArgumentException e = new IllegalArgumentException( + "Incomplete location object, missing timestamp or accuracy? " + loc); if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) { - // for backwards compatibility, allow mock locations that are incomplete - Log.w(TAG, "Incomplete Location object", new Throwable()); + // just log on old platform (for backwards compatibility) + Log.w(TAG, e); loc.makeComplete(); } else { - throw new IllegalArgumentException( - "Location object not complete. Missing timestamps or accuracy?"); + // really throw it! + throw e; } } try { mService.setTestProviderLocation(provider, loc); - } catch (RemoteException ex) { - Log.e(TAG, "setTestProviderLocation: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1265,12 +1381,14 @@ public class LocationManager { * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled * @throws IllegalArgumentException if no provider with the given name exists + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void clearTestProviderLocation(String provider) { try { mService.clearTestProviderLocation(provider); - } catch (RemoteException ex) { - Log.e(TAG, "clearTestProviderLocation: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1285,12 +1403,14 @@ public class LocationManager { * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled * @throws IllegalArgumentException if no provider with the given name exists + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void setTestProviderEnabled(String provider, boolean enabled) { try { mService.setTestProviderEnabled(provider, enabled); - } catch (RemoteException ex) { - Log.e(TAG, "setTestProviderEnabled: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1303,14 +1423,15 @@ public class LocationManager { * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled * @throws IllegalArgumentException if no provider with the given name exists + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void clearTestProviderEnabled(String provider) { try { mService.clearTestProviderEnabled(provider); - } catch (RemoteException ex) { - Log.e(TAG, "clearTestProviderEnabled: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } - } /** @@ -1326,12 +1447,14 @@ public class LocationManager { * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled * @throws IllegalArgumentException if no provider with the given name exists + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated 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); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1344,12 +1467,14 @@ public class LocationManager { * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled * @throws IllegalArgumentException if no provider with the given name exists + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public void clearTestProviderStatus(String provider) { try { mService.clearTestProviderStatus(provider); - } catch (RemoteException ex) { - Log.e(TAG, "clearTestProviderStatus: RemoteException", ex); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); } } @@ -1587,7 +1712,9 @@ public class LocationManager { * The provider may optionally fill the extras Bundle with results from the command. * * @return true if the command succeeds. + * @deprecated use the {@link LocationRequest} class instead */ + @Deprecated public boolean sendExtraCommand(String provider, String command, Bundle extras) { try { return mService.sendExtraCommand(provider, command, extras); @@ -1612,4 +1739,41 @@ public class LocationManager { } } + private static void checkProvider(String provider) { + if (provider == null) { + throw new IllegalArgumentException("invalid provider: " + provider); + } + } + + private static void checkCriteria(Criteria criteria) { + if (criteria == null) { + throw new IllegalArgumentException("invalid criteria: " + criteria); + } + } + private static void checkListener(LocationListener listener) { + if (listener == null) { + throw new IllegalArgumentException("invalid listener: " + listener); + } + } + + private void checkPendingIntent(PendingIntent intent) { + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + intent); + } + if (!intent.isTargetedToPackage()) { + IllegalArgumentException e = new IllegalArgumentException( + "pending intent msut be targeted to package"); + if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) { + throw e; + } else { + Log.w(TAG, e); + } + } + } + + private static void checkGeofence(Geofence fence) { + if (fence == null) { + throw new IllegalArgumentException("invalid geofence: " + fence); + } + } } diff --git a/location/java/android/location/LocationProvider.java b/location/java/android/location/LocationProvider.java index 8c16580..737e17f 100644 --- a/location/java/android/location/LocationProvider.java +++ b/location/java/android/location/LocationProvider.java @@ -16,8 +16,8 @@ package android.location; -import android.os.RemoteException; -import android.util.Log; + +import com.android.internal.location.ProviderProperties; /** * An abstract superclass for location providers. A location provider @@ -32,35 +32,40 @@ import android.util.Log; * characteristics or monetary costs to the user. The {@link * Criteria} class allows providers to be selected based on * user-specified criteria. + * + * @deprecated Use the {@link Criteria} class to request location instead of + * enumerating providers. */ -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 final String mName; - private final ILocationManager mService; - +@Deprecated +public class LocationProvider { public static final int OUT_OF_SERVICE = 0; public static final int TEMPORARILY_UNAVAILABLE = 1; public static final int AVAILABLE = 2; /** + * A regular expression matching characters that may not appear + * in the name of a LocationProvider + * @hide + */ + public static final String BAD_CHARS_REGEX = "[^a-zA-Z0-9]"; + + private final String mName; + private final ProviderProperties mProperties; + + /** * Constructs a LocationProvider with the given name. Provider names must * consist only of the characters [a-zA-Z0-9]. * * @throws IllegalArgumentException if name contains an illegal character * - * {@hide} + * @hide */ - public LocationProvider(String name, ILocationManager service) { + public LocationProvider(String name, ProviderProperties properties) { if (name.matches(BAD_CHARS_REGEX)) { - throw new IllegalArgumentException("name " + name + - " contains an illegal character"); + throw new IllegalArgumentException("provider name contains illegal character: " + name); } mName = name; - mService = service; + mProperties = properties; } /** @@ -75,40 +80,81 @@ public abstract class LocationProvider { * false otherwise. */ public boolean meetsCriteria(Criteria criteria) { - try { - return mService.providerMeetsCriteria(mName, criteria); - } catch (RemoteException e) { - Log.e(TAG, "meetsCriteria: RemoteException", e); + return propertiesMeetCriteria(mName, mProperties, criteria); + } + + /** + * @hide + */ + public static boolean propertiesMeetCriteria(String name, ProviderProperties properties, + Criteria criteria) { + if (LocationManager.PASSIVE_PROVIDER.equals(name)) { + // passive provider never matches + return false; + } + if (properties == null) { + // unfortunately this can happen for provider in remote services + // that have not finished binding yet + return false; + } + + if (criteria.getAccuracy() != Criteria.NO_REQUIREMENT && + criteria.getAccuracy() < properties.mAccuracy) { return false; } + if (criteria.getPowerRequirement() != Criteria.NO_REQUIREMENT && + criteria.getPowerRequirement() < properties.mPowerRequirement) { + return false; + } + if (criteria.isAltitudeRequired() && !properties.mSupportsAltitude) { + return false; + } + if (criteria.isSpeedRequired() && !properties.mSupportsSpeed) { + return false; + } + if (criteria.isBearingRequired() && !properties.mSupportsBearing) { + return false; + } + if (!criteria.isCostAllowed() && properties.mHasMonetaryCost) { + return false; + } + return true; } /** * Returns true if the provider requires access to a * data network (e.g., the Internet), false otherwise. */ - public abstract boolean requiresNetwork(); + public boolean requiresNetwork() { + return mProperties.mRequiresNetwork; + } /** * Returns true if the provider requires access to a * satellite-based positioning system (e.g., GPS), false * otherwise. */ - public abstract boolean requiresSatellite(); + public boolean requiresSatellite() { + return mProperties.mRequiresSatellite; + } /** * Returns true if the provider requires access to an appropriate * cellular network (e.g., to make use of cell tower IDs), false * otherwise. */ - public abstract boolean requiresCell(); + public boolean requiresCell() { + return mProperties.mRequiresCell; + } /** * Returns true if the use of this provider may result in a * monetary charge to the user, false if use is free. It is up to * each provider to give accurate information. */ - public abstract boolean hasMonetaryCost(); + public boolean hasMonetaryCost() { + return mProperties.mHasMonetaryCost; + } /** * Returns true if the provider is able to provide altitude @@ -116,7 +162,9 @@ public abstract class LocationProvider { * under most circumstances but may occassionally not report it * should return true. */ - public abstract boolean supportsAltitude(); + public boolean supportsAltitude() { + return mProperties.mSupportsAltitude; + } /** * Returns true if the provider is able to provide speed @@ -124,7 +172,9 @@ public abstract class LocationProvider { * under most circumstances but may occassionally not report it * should return true. */ - public abstract boolean supportsSpeed(); + public boolean supportsSpeed() { + return mProperties.mSupportsSpeed; + } /** * Returns true if the provider is able to provide bearing @@ -132,7 +182,9 @@ public abstract class LocationProvider { * under most circumstances but may occassionally not report it * should return true. */ - public abstract boolean supportsBearing(); + public boolean supportsBearing() { + return mProperties.mSupportsBearing; + } /** * Returns the power requirement for this provider. @@ -140,7 +192,9 @@ public abstract class LocationProvider { * @return the power requirement for this provider, as one of the * constants Criteria.POWER_REQUIREMENT_*. */ - public abstract int getPowerRequirement(); + public int getPowerRequirement() { + return mProperties.mPowerRequirement; + } /** * Returns a constant describing horizontal accuracy of this provider. @@ -149,5 +203,7 @@ public abstract class LocationProvider { * location is only approximate then {@link Criteria#ACCURACY_COARSE} * is returned. */ - public abstract int getAccuracy(); + public int getAccuracy() { + return mProperties.mAccuracy; + } } diff --git a/location/java/android/location/LocationRequest.aidl b/location/java/android/location/LocationRequest.aidl new file mode 100644 index 0000000..b1a8647 --- /dev/null +++ b/location/java/android/location/LocationRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +parcelable LocationRequest; diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java new file mode 100644 index 0000000..3110196 --- /dev/null +++ b/location/java/android/location/LocationRequest.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.TimeUtils; + +public final class LocationRequest implements Parcelable { + // QOS control + public static final int ACCURACY_FINE = 100; // ~1 meter + public static final int ACCURACY_BLOCK = 102; // ~100 meters + public static final int ACCURACY_CITY = 104; // ~10 km + public static final int POWER_NONE = 200; + public static final int POWER_LOW = 201; + public static final int POWER_HIGH = 203; + + private int mQuality = POWER_LOW; + private long mFastestInterval = 6 * 1000; // 6 seconds + private long mInterval = 60 * 1000; // 1 minute + private long mExpireAt = Long.MAX_VALUE; // no expiry + private int mNumUpdates = Integer.MAX_VALUE; // no expiry + private float mSmallestDisplacement = 0.0f; // meters + + private String mProvider = null; // for deprecated API's that explicitly request a provider + + public static LocationRequest create() { + LocationRequest request = new LocationRequest(); + return request; + } + + /** @hide */ + public static LocationRequest createFromDeprecatedProvider(String provider, long minTime, + float minDistance, boolean singleShot) { + if (minTime < 0) minTime = 0; + if (minDistance < 0) minDistance = 0; + + int quality; + if (LocationManager.PASSIVE_PROVIDER.equals(provider)) { + quality = POWER_NONE; + } else if (LocationManager.GPS_PROVIDER.equals(provider)) { + quality = ACCURACY_FINE; + } else { + quality = POWER_LOW; + } + + LocationRequest request = new LocationRequest() + .setProvider(provider) + .setQuality(quality) + .setInterval(minTime) + .setFastestInterval(minTime) + .setSmallestDisplacement(minDistance); + if (singleShot) request.setNumUpdates(1); + return request; + } + + /** @hide */ + public static LocationRequest createFromDeprecatedCriteria(Criteria criteria, long minTime, + float minDistance, boolean singleShot) { + if (minTime < 0) minTime = 0; + if (minDistance < 0) minDistance = 0; + + int quality; + switch (criteria.getAccuracy()) { + case Criteria.ACCURACY_COARSE: + quality = ACCURACY_BLOCK; + break; + case Criteria.ACCURACY_FINE: + quality = ACCURACY_FINE; + break; + default: { + switch (criteria.getPowerRequirement()) { + case Criteria.POWER_HIGH: + quality = POWER_HIGH; + default: + quality = POWER_LOW; + } + } + } + + LocationRequest request = new LocationRequest() + .setQuality(quality) + .setInterval(minTime) + .setFastestInterval(minTime) + .setSmallestDisplacement(minDistance); + if (singleShot) request.setNumUpdates(1); + return request; + } + + /** @hide */ + public LocationRequest() { } + + public LocationRequest setQuality(int quality) { + checkQuality(quality); + mQuality = quality; + return this; + } + + public int getQuality() { + return mQuality; + } + + public LocationRequest setInterval(long millis) { + checkInterval(millis); + mInterval = millis; + return this; + } + + public long getInterval() { + return mInterval; + } + + public LocationRequest setFastestInterval(long millis) { + checkInterval(millis); + mFastestInterval = millis; + return this; + } + + public long getFastestInterval() { + return mFastestInterval; + } + + public LocationRequest setExpireIn(long millis) { + mExpireAt = millis + SystemClock.elapsedRealtime(); + if (mExpireAt < 0) mExpireAt = 0; + return this; + } + + public LocationRequest setExpireAt(long millis) { + mExpireAt = millis; + if (mExpireAt < 0) mExpireAt = 0; + return this; + } + + public long getExpireAt() { + return mExpireAt; + } + + public int getNumUpdates() { + return mNumUpdates; + } + + /** @hide */ + public void decrementNumUpdates() { + if (mNumUpdates != Integer.MAX_VALUE) { + mNumUpdates--; + } + if (mNumUpdates < 0) { + mNumUpdates = 0; + } + } + + public LocationRequest setNumUpdates(int numUpdates) { + if (numUpdates < 0) throw new IllegalArgumentException("invalid numUpdates: " + numUpdates); + mNumUpdates = numUpdates; + return this; + } + + /** @hide */ + public LocationRequest setProvider(String provider) { + checkProvider(provider); + mProvider = provider; + return this; + } + + /** @hide */ + public String getProvider() { + return mProvider; + } + + /** @hide */ + public LocationRequest setSmallestDisplacement(float meters) { + checkDisplacement(meters); + mSmallestDisplacement = meters; + return this; + } + + /** @hide */ + public float getSmallestDisplacement() { + return mSmallestDisplacement; + } + + /** @hide */ + public LocationRequest applyCoarsePermissionRestrictions() { + switch (mQuality) { + case ACCURACY_FINE: + mQuality = ACCURACY_BLOCK; + break; + } + // cap fastest interval to 6 seconds + if (mFastestInterval < 6 * 1000) mFastestInterval = 6 * 1000; + // cap requested interval to 1 minute + if (mInterval < 60 * 1000) mInterval = 60 * 1000; + return this; + } + + private static void checkInterval(long millis) { + if (millis < 0) { + throw new IllegalArgumentException("invalid interval: " + millis); + } + } + + private static void checkQuality(int quality) { + switch (quality) { + case ACCURACY_FINE: + case ACCURACY_BLOCK: + case ACCURACY_CITY: + case POWER_NONE: + case POWER_LOW: + case POWER_HIGH: + break; + default: + throw new IllegalArgumentException("invalid quality: " + quality); + } + } + + private static void checkDisplacement(float meters) { + if (meters < 0.0f) { + throw new IllegalArgumentException("invalid displacement: " + meters); + } + } + + private static void checkProvider(String name) { + if (name == null) { + throw new IllegalArgumentException("invalid provider: " + name); + } + } + + public static final Parcelable.Creator<LocationRequest> CREATOR = + new Parcelable.Creator<LocationRequest>() { + @Override + public LocationRequest createFromParcel(Parcel in) { + LocationRequest request = new LocationRequest(); + request.setQuality(in.readInt()); + request.setFastestInterval(in.readLong()); + request.setInterval(in.readLong()); + request.setExpireAt(in.readLong()); + request.setNumUpdates(in.readInt()); + request.setSmallestDisplacement(in.readFloat()); + String provider = in.readString(); + if (provider != null) request.setProvider(provider); + return request; + } + @Override + public LocationRequest[] newArray(int size) { + return new LocationRequest[size]; + } + }; + @Override + public int describeContents() { + return 0; + } + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mQuality); + parcel.writeLong(mFastestInterval); + parcel.writeLong(mInterval); + parcel.writeLong(mExpireAt); + parcel.writeInt(mNumUpdates); + parcel.writeFloat(mSmallestDisplacement); + parcel.writeString(mProvider); + } + + /** @hide */ + public static String qualityToString(int quality) { + switch (quality) { + case ACCURACY_FINE: + return "ACCURACY_FINE"; + case ACCURACY_BLOCK: + return "ACCURACY_BLOCK"; + case ACCURACY_CITY: + return "ACCURACY_CITY"; + case POWER_NONE: + return "POWER_NONE"; + case POWER_LOW: + return "POWER_LOW"; + case POWER_HIGH: + return "POWER_HIGH"; + default: + return "???"; + } + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Request[").append(qualityToString(mQuality)); + if (mProvider != null) s.append(' ').append(mProvider); + if (mQuality != POWER_NONE) { + s.append(" requested="); + TimeUtils.formatDuration(mInterval, s); + } + s.append(" fastest="); + TimeUtils.formatDuration(mFastestInterval, s); + if (mExpireAt != Long.MAX_VALUE) { + long expireIn = mExpireAt - SystemClock.elapsedRealtime(); + s.append(" expireIn="); + TimeUtils.formatDuration(expireIn, s); + } + if (mNumUpdates != Integer.MAX_VALUE){ + s.append(" num=").append(mNumUpdates); + } + s.append(']'); + return s.toString(); + } +} diff --git a/location/java/com/android/internal/location/DummyLocationProvider.java b/location/java/com/android/internal/location/DummyLocationProvider.java deleted file mode 100644 index 3122960..0000000 --- a/location/java/com/android/internal/location/DummyLocationProvider.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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.location.ILocationManager; -import android.location.LocationProvider; - -/** - * 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} - */ -public 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; - - public DummyLocationProvider(String name, ILocationManager service) { - super(name, service); - } - - 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. - */ - @Override - public boolean requiresNetwork() { - return mRequiresNetwork; - } - - /** - * Returns true if the provider requires access to a - * satellite-based positioning system (e.g., GPS), false - * otherwise. - */ - @Override - 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. - */ - @Override - 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. - */ - @Override - 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. - */ - @Override - 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. - */ - @Override - 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. - */ - @Override - 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_*. - */ - @Override - 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_*. - */ - @Override - public int getAccuracy() { - return mAccuracy; - } -} - diff --git a/location/java/android/location/ILocationProvider.aidl b/location/java/com/android/internal/location/ILocationProvider.aidl index ecf6789..39c2d92 100644 --- a/location/java/android/location/ILocationProvider.aidl +++ b/location/java/com/android/internal/location/ILocationProvider.aidl @@ -14,40 +14,31 @@ * limitations under the License. */ -package android.location; +package com.android.internal.location; -import android.location.Criteria; import android.location.Location; import android.net.NetworkInfo; import android.os.Bundle; import android.os.WorkSource; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + /** * Binder interface for services that implement location providers. - * - * {@hide} + * <p>Use {@link LocationProviderBase} as a helper to implement this + * interface. + * @hide */ interface ILocationProvider { - boolean requiresNetwork(); - boolean requiresSatellite(); - boolean requiresCell(); - boolean hasMonetaryCost(); - boolean supportsAltitude(); - boolean supportsSpeed(); - boolean supportsBearing(); - int getPowerRequirement(); - boolean meetsCriteria(in Criteria criteria); - int getAccuracy(); void enable(); void disable(); + + void setRequest(in ProviderRequest request, in WorkSource ws); + + // --- deprecated (but still supported) --- + ProviderProperties getProperties(); int getStatus(out Bundle extras); long getStatusUpdateTime(); - String getInternalState(); - void enableLocationTracking(boolean enable); - void setMinTime(long minTime, in WorkSource ws); - void updateNetworkState(int state, in NetworkInfo info); - void updateLocation(in Location location); boolean sendExtraCommand(String command, inout Bundle extras); - void addListener(int uid); - void removeListener(int uid); } diff --git a/location/java/com/android/internal/location/ProviderProperties.aidl b/location/java/com/android/internal/location/ProviderProperties.aidl new file mode 100644 index 0000000..b901444 --- /dev/null +++ b/location/java/com/android/internal/location/ProviderProperties.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +parcelable ProviderProperties; diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java new file mode 100644 index 0000000..08aed80 --- /dev/null +++ b/location/java/com/android/internal/location/ProviderProperties.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A Parcelable containing (legacy) location provider properties. + * This object is just used inside the framework and system services. + * @hide + */ +public final class ProviderProperties implements Parcelable { + /** + * True if provider requires access to a + * data network (e.g., the Internet), false otherwise. + */ + public final boolean mRequiresNetwork; + + /** + * True if the provider requires access to a + * satellite-based positioning system (e.g., GPS), false + * otherwise. + */ + public final boolean mRequiresSatellite; + + /** + * True if the provider requires access to an appropriate + * cellular network (e.g., to make use of cell tower IDs), false + * otherwise. + */ + public final boolean mRequiresCell; + + /** + * 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. Cell (network) usage + * is not considered monetary cost. + */ + public final boolean mHasMonetaryCost; + + /** + * True if the provider is able to provide altitude + * information, false otherwise. A provider that reports altitude + * under most circumstances but may occasionally not report it + * should return true. + */ + public final boolean mSupportsAltitude; + + /** + * True if the provider is able to provide speed + * information, false otherwise. A provider that reports speed + * under most circumstances but may occasionally not report it + * should return true. + */ + public final boolean mSupportsSpeed; + + /** + * True if the provider is able to provide bearing + * information, false otherwise. A provider that reports bearing + * under most circumstances but may occasionally not report it + * should return true. + */ + public final boolean mSupportsBearing; + + /** + * Power requirement for this provider. + * + * @return the power requirement for this provider, as one of the + * constants Criteria.POWER_*. + */ + public final int mPowerRequirement; + + /** + * Constant describing the horizontal accuracy returned + * by this provider. + * + * @return the horizontal accuracy for this provider, as one of the + * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE + */ + public final int mAccuracy; + + public ProviderProperties(boolean mRequiresNetwork, + boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost, + boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing, + int mPowerRequirement, int mAccuracy) { + this.mRequiresNetwork = mRequiresNetwork; + this.mRequiresSatellite = mRequiresSatellite; + this.mRequiresCell = mRequiresCell; + this.mHasMonetaryCost = mHasMonetaryCost; + this.mSupportsAltitude = mSupportsAltitude; + this.mSupportsSpeed = mSupportsSpeed; + this.mSupportsBearing = mSupportsBearing; + this.mPowerRequirement = mPowerRequirement; + this.mAccuracy = mAccuracy; + } + + public static final Parcelable.Creator<ProviderProperties> CREATOR = + new Parcelable.Creator<ProviderProperties>() { + @Override + public ProviderProperties createFromParcel(Parcel in) { + boolean requiresNetwork = in.readInt() == 1; + boolean requiresSatellite = in.readInt() == 1; + boolean requiresCell = in.readInt() == 1; + boolean hasMonetaryCost = in.readInt() == 1; + boolean supportsAltitude = in.readInt() == 1; + boolean supportsSpeed = in.readInt() == 1; + boolean supportsBearing = in.readInt() == 1; + int powerRequirement = in.readInt(); + int accuracy = in.readInt(); + return new ProviderProperties(requiresNetwork, requiresSatellite, + requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing, + powerRequirement, accuracy); + } + @Override + public ProviderProperties[] newArray(int size) { + return new ProviderProperties[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mRequiresNetwork ? 1 : 0); + parcel.writeInt(mRequiresSatellite ? 1 : 0); + parcel.writeInt(mRequiresCell ? 1 : 0); + parcel.writeInt(mHasMonetaryCost ? 1 : 0); + parcel.writeInt(mSupportsAltitude ? 1 : 0); + parcel.writeInt(mSupportsSpeed ? 1 : 0); + parcel.writeInt(mSupportsSpeed ? 1 : 0); + parcel.writeInt(mPowerRequirement); + parcel.writeInt(mAccuracy); + } +} diff --git a/location/java/com/android/internal/location/ProviderRequest.aidl b/location/java/com/android/internal/location/ProviderRequest.aidl new file mode 100644 index 0000000..4e1ea95 --- /dev/null +++ b/location/java/com/android/internal/location/ProviderRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +parcelable ProviderRequest; diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java new file mode 100644 index 0000000..25c51f5 --- /dev/null +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import java.util.ArrayList; +import java.util.List; + +import android.location.LocationRequest; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.TimeUtils; + +/** @hide */ +public final class ProviderRequest implements Parcelable { + /** Location reporting is requested (true) */ + public boolean reportLocation = false; + + /** The smallest requested interval */ + public long interval = Long.MAX_VALUE; + + /** + * A more detailed set of requests. + * <p>Location Providers can optionally use this to + * fine tune location updates, for example when there + * is a high power slow interval request and a + * low power fast interval request. + */ + public List<LocationRequest> locationRequests = null; + + public ProviderRequest() { + } + + public static final Parcelable.Creator<ProviderRequest> CREATOR = + new Parcelable.Creator<ProviderRequest>() { + @Override + public ProviderRequest createFromParcel(Parcel in) { + ProviderRequest request = new ProviderRequest(); + request.reportLocation = in.readInt() == 1; + request.interval = in.readLong(); + int count = in.readInt(); + request.locationRequests = new ArrayList<LocationRequest>(count); + for (int i = 0; i < count; i++) { + request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); + } + return request; + } + @Override + public ProviderRequest[] newArray(int size) { + return new ProviderRequest[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(reportLocation ? 1 : 0); + parcel.writeLong(interval); + parcel.writeParcelableArray(locationRequests.toArray( + new LocationRequest[locationRequests.size()]), 0); + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("ProviderRequest["); + if (reportLocation) { + s.append("ON"); + s.append(" interval="); + TimeUtils.formatDuration(interval, s); + } else { + s.append("OFF"); + } + s.append(']'); + return s.toString(); + } +} diff --git a/location/lib/Android.mk b/location/lib/Android.mk index a06478a..62f5677 100644 --- a/location/lib/Android.mk +++ b/location/lib/Android.mk @@ -23,7 +23,8 @@ LOCAL_MODULE:= com.android.location.provider LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := \ - $(call all-subdir-java-files) + $(call all-subdir-java-files) \ + $(call all-aidl-files-under, java) include $(BUILD_JAVA_LIBRARY) diff --git a/location/lib/java/com/android/location/provider/LocationProvider.java b/location/lib/java/com/android/location/provider/LocationProvider.java deleted file mode 100644 index 3714f40..0000000 --- a/location/lib/java/com/android/location/provider/LocationProvider.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.location.provider; - -import android.content.Context; -import android.net.NetworkInfo; -import android.location.Criteria; -import android.location.ILocationManager; -import android.location.ILocationProvider; -import android.location.Location; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.WorkSource; -import android.util.Log; - -/** - * An abstract superclass for location providers that are implemented - * outside of the core android platform. - * Location providers can be implemented as services and return the result of - * {@link LocationProvider#getBinder()} in its getBinder() method. - * - * @hide - */ -public abstract class LocationProvider { - - private static final String TAG = "LocationProvider"; - - private ILocationManager mLocationManager; - - private ILocationProvider.Stub mProvider = new ILocationProvider.Stub() { - - public boolean requiresNetwork() { - return LocationProvider.this.onRequiresNetwork(); - } - - public boolean requiresSatellite() { - return LocationProvider.this.onRequiresSatellite(); - } - - public boolean requiresCell() { - return LocationProvider.this.onRequiresCell(); - } - - public boolean hasMonetaryCost() { - return LocationProvider.this.onHasMonetaryCost(); - } - - public boolean supportsAltitude() { - return LocationProvider.this.onSupportsAltitude(); - } - - public boolean supportsSpeed() { - return LocationProvider.this.onSupportsSpeed(); - } - - public boolean supportsBearing() { - return LocationProvider.this.onSupportsBearing(); - } - - public int getPowerRequirement() { - return LocationProvider.this.onGetPowerRequirement(); - } - - public boolean meetsCriteria(Criteria criteria) { - return LocationProvider.this.onMeetsCriteria(criteria); - } - - public int getAccuracy() { - return LocationProvider.this.onGetAccuracy(); - } - - public void enable() { - LocationProvider.this.onEnable(); - } - - public void disable() { - LocationProvider.this.onDisable(); - } - - public int getStatus(Bundle extras) { - return LocationProvider.this.onGetStatus(extras); - } - - public long getStatusUpdateTime() { - return LocationProvider.this.onGetStatusUpdateTime(); - } - - public String getInternalState() { - return LocationProvider.this.onGetInternalState(); - } - - public void enableLocationTracking(boolean enable) { - LocationProvider.this.onEnableLocationTracking(enable); - } - - public void setMinTime(long minTime, WorkSource ws) { - LocationProvider.this.onSetMinTime(minTime, ws); - } - - public void updateNetworkState(int state, NetworkInfo info) { - LocationProvider.this.onUpdateNetworkState(state, info); - } - - public void updateLocation(Location location) { - LocationProvider.this.onUpdateLocation(location); - } - - public boolean sendExtraCommand(String command, Bundle extras) { - return LocationProvider.this.onSendExtraCommand(command, extras); - } - - public void addListener(int uid) { - LocationProvider.this.onAddListener(uid, new WorkSource(uid)); - } - - public void removeListener(int uid) { - LocationProvider.this.onRemoveListener(uid, new WorkSource(uid)); - } - }; - - public LocationProvider() { - IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE); - mLocationManager = ILocationManager.Stub.asInterface(b); - } - - /** - * {@hide} - */ - /* package */ ILocationProvider getInterface() { - return mProvider; - } - - /** - * Returns the Binder interface for the location provider. - * This is intended to be used for the onBind() method of - * a service that implements a location provider service. - * - * @return the IBinder instance for the provider - */ - public IBinder getBinder() { - return mProvider; - } - - /** - * Used by the location provider to report new locations. - * - * @param location new Location to report - * - * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission. - */ - public void reportLocation(Location location) { - try { - mLocationManager.reportLocation(location, false); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in reportLocation: ", e); - } - } - - /** - * Returns true if the provider requires access to a - * data network (e.g., the Internet), false otherwise. - */ - public abstract boolean onRequiresNetwork(); - - /** - * Returns true if the provider requires access to a - * satellite-based positioning system (e.g., GPS), false - * otherwise. - */ - public abstract boolean onRequiresSatellite(); - - /** - * 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 onRequiresCell(); - - /** - * 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 onHasMonetaryCost(); - - /** - * 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 onSupportsAltitude(); - - /** - * 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 onSupportsSpeed(); - - /** - * 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 onSupportsBearing(); - - /** - * 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 onGetPowerRequirement(); - - /** - * Returns true if this provider meets the given criteria, - * false otherwise. - */ - public abstract boolean onMeetsCriteria(Criteria criteria); - - /** - * 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 onGetAccuracy(); - - /** - * Enables the location provider - */ - public abstract void onEnable(); - - /** - * Disables the location provider - */ - public abstract void onDisable(); - - /** - * Returns a information on the status of this provider. - * {@link android.location.LocationProvider#OUT_OF_SERVICE} is returned if the provider is - * out of service, and this is not expected to change in the near - * future; {@link android.location.LocationProvider#TEMPORARILY_UNAVAILABLE} is returned if - * the provider is temporarily unavailable but is expected to be - * available shortly; and {@link android.location.LocationProvider#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 onGetStatus(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 SystemClock.elapsedRealtime()}. - * 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 abstract long onGetStatusUpdateTime(); - - /** - * Returns debugging information about the location provider. - * - * @return string describing the internal state of the location provider, or null. - */ - public abstract String onGetInternalState(); - - /** - * 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 abstract void onEnableLocationTracking(boolean enable); - - /** - * 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. - * @param ws the source this work is coming from. - */ - public abstract void onSetMinTime(long minTime, WorkSource ws); - - /** - * Updates the network state for the given provider. This function must - * be overwritten if {@link android.location.LocationProvider#requiresNetwork} returns true. - * The state is {@link android.location.LocationProvider#TEMPORARILY_UNAVAILABLE} (disconnected) - * OR {@link android.location.LocationProvider#AVAILABLE} (connected or connecting). - * - * @param state data state - */ - public abstract void onUpdateNetworkState(int state, NetworkInfo info); - - /** - * Informs the provider when a new location has been computed by a different - * location provider. This is intended to be used as aiding data for the - * receiving provider. - * - * @param location new location from other location provider - */ - public abstract void onUpdateLocation(Location location); - - /** - * 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 abstract boolean onSendExtraCommand(String command, Bundle extras); - - /** - * Notifies the location provider when a new client is listening for locations. - * - * @param uid user ID of the new client. - * @param ws a WorkSource representation of the client. - */ - public abstract void onAddListener(int uid, WorkSource ws); - - /** - * Notifies the location provider when a client is no longer listening for locations. - * - * @param uid user ID of the client no longer listening. - * @param ws a WorkSource representation of the client. - */ - public abstract void onRemoveListener(int uid, WorkSource ws); -} diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java new file mode 100644 index 0000000..53b0cae --- /dev/null +++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.location.provider; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.PrintWriter; + +import android.content.Context; +import android.location.ILocationManager; +import android.location.Location; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.WorkSource; +import android.util.Log; + +import com.android.internal.location.ILocationProvider; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + + +/** + * Base class for location providers implemented as services. + * @hide + */ +public abstract class LocationProviderBase { + private final String TAG; + + protected final ILocationManager mLocationManager; + private final ProviderProperties mProperties; + private final IBinder mBinder; + + private final class Service extends ILocationProvider.Stub { + @Override + public void enable() { + onEnable(); + } + @Override + public void disable() { + onDisable(); + } + @Override + public void setRequest(ProviderRequest request, WorkSource ws) { + onSetRequest(new ProviderRequestUnbundled(request), ws); + } + @Override + public ProviderProperties getProperties() { + return mProperties; + } + @Override + public int getStatus(Bundle extras) { + return onGetStatus(extras); + } + @Override + public long getStatusUpdateTime() { + return onGetStatusUpdateTime(); + } + @Override + public boolean sendExtraCommand(String command, Bundle extras) { + return onSendExtraCommand(command, extras); + } + @Override + public void dump(FileDescriptor fd, String[] args) { + PrintWriter pw = new PrintWriter(new FileOutputStream(fd)); + onDump(fd, pw, args); + pw.flush(); + } + } + + public LocationProviderBase(String tag, ProviderPropertiesUnbundled properties) { + TAG = tag; + IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE); + mLocationManager = ILocationManager.Stub.asInterface(b); + mProperties = properties.getProviderProperties(); + mBinder = new Service(); + } + + public IBinder getBinder() { + return mBinder; + } + + /** + * Used by the location provider to report new locations. + * + * @param location new Location to report + * + * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission. + */ + public final void reportLocation(Location location) { + try { + mLocationManager.reportLocation(location, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException", e); + } catch (Exception e) { + // never crash provider, might be running in a system process + Log.e(TAG, "Exception", e); + } + } + + /** + * Enable the location provider. + * <p>The provider may initialize resources, but does + * not yet need to report locations. + */ + public abstract void onEnable(); + + /** + * Disable the location provider. + * <p>The provider must release resources, and stop + * performing work. It may no longer report locations. + */ + public abstract void onDisable(); + + /** + * Set the {@link ProviderRequest} requirements for this provider. + * <p>Each call to this method overrides all previous requests. + * <p>This method might trigger the provider to start returning + * locations, or to stop returning locations, depending on the + * parameters in the request. + */ + public abstract void onSetRequest(ProviderRequestUnbundled request, WorkSource source); + + /** + * Dump debug information. + */ + public void onDump(FileDescriptor fd, PrintWriter pw, String[] args) { + } + + /** + * Returns a information on the status of this provider. + * <p>{@link android.location.LocationProvider#OUT_OF_SERVICE} is returned if the provider is + * out of service, and this is not expected to change in the near + * future; {@link android.location.LocationProvider#TEMPORARILY_UNAVAILABLE} is returned if + * the provider is temporarily unavailable but is expected to be + * available shortly; and {@link android.location.LocationProvider#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 onGetStatus(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 SystemClock.elapsedRealtime()}. + * 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 abstract long onGetStatusUpdateTime(); + + /** + * 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 onSendExtraCommand(String command, Bundle extras) { + // default implementation + return false; + } +} diff --git a/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java b/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java new file mode 100644 index 0000000..c8bdda4 --- /dev/null +++ b/location/lib/java/com/android/location/provider/ProviderPropertiesUnbundled.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.location.provider; + +import com.android.internal.location.ProviderProperties; + +/** + * This class is a public API for unbundled providers, + * that hides the (hidden framework) ProviderProperties. + * <p>Do _not_ remove public methods on this class. + */ +public final class ProviderPropertiesUnbundled { + private final ProviderProperties mProperties; + + public static ProviderPropertiesUnbundled create(boolean requiresNetwork, + boolean requiresSatellite, boolean requiresCell, boolean hasMonetaryCost, + boolean supportsAltitude, boolean supportsSpeed, boolean supportsBearing, + int powerRequirement, int accuracy) { + return new ProviderPropertiesUnbundled(new ProviderProperties(requiresNetwork, + requiresSatellite, requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, + supportsBearing, powerRequirement, accuracy)); + } + + private ProviderPropertiesUnbundled(ProviderProperties properties) { + mProperties = properties; + } + + public ProviderProperties getProviderProperties() { + return mProperties; + } + + @Override + public String toString() { + return mProperties.toString(); + } +} diff --git a/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java new file mode 100644 index 0000000..7487a56 --- /dev/null +++ b/location/lib/java/com/android/location/provider/ProviderRequestUnbundled.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.location.provider; + +import java.util.List; + +import android.location.LocationRequest; + +import com.android.internal.location.ProviderRequest; + +/** + * This class is a public API for unbundled providers, + * that hides the (hidden framework) ProviderRequest. + * <p>Do _not_ remove public methods on this class. + */ +public final class ProviderRequestUnbundled { + private final ProviderRequest mRequest; + + public ProviderRequestUnbundled(ProviderRequest request) { + mRequest = request; + } + + public boolean getReportLocation() { + return mRequest.reportLocation; + } + + public long getInterval() { + return mRequest.interval; + } + + public List<LocationRequest> getLocationRequests() { + return mRequest.locationRequests; + } + + @Override + public String toString() { + return mRequest.toString(); + } +} diff --git a/packages/FusedLocation/Android.mk b/packages/FusedLocation/Android.mk new file mode 100644 index 0000000..318782f --- /dev/null +++ b/packages/FusedLocation/Android.mk @@ -0,0 +1,27 @@ +# Copyright (C) 2012 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_JAVA_LIBRARIES := com.android.location.provider + +LOCAL_PACKAGE_NAME := FusedLocation +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/packages/FusedLocation/AndroidManifest.xml b/packages/FusedLocation/AndroidManifest.xml new file mode 100644 index 0000000..4c57401 --- /dev/null +++ b/packages/FusedLocation/AndroidManifest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright (c) 2012 Google Inc. + * + * 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. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.location.fused" + coreApp="true"> + + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" /> + + <application + android:label="@string/app_label" + android:process="system"> + + <uses-library android:name="com.android.location.provider" /> + + <!-- Fused Location Service that LocationManagerService binds to. + LocationManagerService will bind to the service with the highest + version. --> + <service android:name="com.android.location.fused.FusedLocationService" + android:exported="true" + android:permission="android.permission.WRITE_SECURE_SETTINGS" > + <intent-filter> + <action android:name="com.android.location.service.FusedLocationProvider" /> + </intent-filter> + <meta-data android:name="version" android:value="1" /> + </service> + </application> +</manifest> diff --git a/packages/FusedLocation/MODULE_LICENSE_APACHE2 b/packages/FusedLocation/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/packages/FusedLocation/MODULE_LICENSE_APACHE2 diff --git a/packages/FusedLocation/NOTICE b/packages/FusedLocation/NOTICE new file mode 100644 index 0000000..33ff961 --- /dev/null +++ b/packages/FusedLocation/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2012, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/packages/FusedLocation/res/values/strings.xml b/packages/FusedLocation/res/values/strings.xml new file mode 100644 index 0000000..5b78e39 --- /dev/null +++ b/packages/FusedLocation/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Name of the application. [CHAR LIMIT=35] --> + <string name="app_label">Fused Location</string> +</resources> diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java new file mode 100644 index 0000000..45f05f3 --- /dev/null +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationProvider.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.location.fused; + + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.android.location.provider.LocationProviderBase; +import com.android.location.provider.ProviderPropertiesUnbundled; +import com.android.location.provider.ProviderRequestUnbundled; + +import android.content.Context; +import android.location.Criteria; +import android.location.LocationProvider; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.WorkSource; + +public class FusedLocationProvider extends LocationProviderBase implements FusionEngine.Callback { + private static final String TAG = "FusedLocationProvider"; + + private static ProviderPropertiesUnbundled PROPERTIES = ProviderPropertiesUnbundled.create( + false, false, false, false, true, true, true, Criteria.POWER_LOW, + Criteria.ACCURACY_FINE); + + private static final int MSG_ENABLE = 1; + private static final int MSG_DISABLE = 2; + private static final int MSG_SET_REQUEST = 3; + + private final Context mContext; + private final FusionEngine mEngine; + + private static class RequestWrapper { + public ProviderRequestUnbundled request; + public WorkSource source; + public RequestWrapper(ProviderRequestUnbundled request, WorkSource source) { + this.request = request; + this.source = source; + } + } + + public FusedLocationProvider(Context context) { + super(TAG, PROPERTIES); + mContext = context; + mEngine = new FusionEngine(context, Looper.myLooper()); + } + + /** + * For serializing requests to mEngine. + */ + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ENABLE: + mEngine.init(FusedLocationProvider.this); + break; + case MSG_DISABLE: + mEngine.deinit(); + break; + case MSG_SET_REQUEST: + { + RequestWrapper wrapper = (RequestWrapper) msg.obj; + mEngine.setRequirements(wrapper.request, wrapper.source); + break; + } + } + } + }; + + @Override + public void onEnable() { + mHandler.sendEmptyMessage(MSG_ENABLE); + } + + @Override + public void onDisable() { + mHandler.sendEmptyMessage(MSG_DISABLE); + } + + @Override + public void onSetRequest(ProviderRequestUnbundled request, WorkSource source) { + mHandler.obtainMessage(MSG_SET_REQUEST, new RequestWrapper(request, source)); + } + + @Override + public void onDump(FileDescriptor fd, PrintWriter pw, String[] args) { + // perform synchronously + mEngine.dump(fd, pw, args); + } + + @Override + public int onGetStatus(Bundle extras) { + return LocationProvider.AVAILABLE; + } + + @Override + public long onGetStatusUpdateTime() { + return 0; + } +} diff --git a/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java b/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java new file mode 100644 index 0000000..509c010 --- /dev/null +++ b/packages/FusedLocation/src/com/android/location/fused/FusedLocationService.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.location.fused; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class FusedLocationService extends Service { + private FusedLocationProvider mProvider; + + @Override + public IBinder onBind(Intent intent) { + if (mProvider == null) { + mProvider = new FusedLocationProvider(getApplicationContext()); + } + return mProvider.getBinder(); + } + + @Override + public boolean onUnbind(Intent intent) { + // make sure to stop performing work + if (mProvider != null) { + mProvider.onDisable(); + } + return false; + } + + @Override + public void onDestroy() { + mProvider = null; + } +} diff --git a/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java new file mode 100644 index 0000000..f4f87a8 --- /dev/null +++ b/packages/FusedLocation/src/com/android/location/fused/FusionEngine.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.location.fused; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; + +import com.android.location.provider.ProviderRequestUnbundled; + + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationRequest; +import android.os.Bundle; +import android.os.Looper; +import android.os.SystemClock; +import android.os.WorkSource; +import android.util.Log; + +public class FusionEngine implements LocationListener { + public interface Callback { + public void reportLocation(Location location); + } + + private static final String TAG = "FusedLocation"; + private static final String NETWORK = LocationManager.NETWORK_PROVIDER; + private static final String GPS = LocationManager.GPS_PROVIDER; + + // threshold below which a location is considered stale enough + // that we shouldn't use its bearing, altitude, speed etc + private static final double WEIGHT_THRESHOLD = 0.5; + // accuracy in meters at which a Location's weight is halved (compared to 0 accuracy) + private static final double ACCURACY_HALFLIFE_M = 20.0; + // age in seconds at which a Location's weight is halved (compared to 0 age) + private static final double AGE_HALFLIFE_S = 60.0; + + private static final double ACCURACY_DECAY_CONSTANT_M = Math.log(2) / ACCURACY_HALFLIFE_M; + private static final double AGE_DECAY_CONSTANT_S = Math.log(2) / AGE_HALFLIFE_S; + + private final Context mContext; + private final LocationManager mLocationManager; + private final Looper mLooper; + + // all fields are only used on mLooper thread. except for in dump() which is not thread-safe + private Callback mCallback; + private Location mFusedLocation; + private Location mGpsLocation; + private Location mNetworkLocation; + private double mNetworkWeight; + private double mGpsWeight; + + private boolean mEnabled; + private ProviderRequestUnbundled mRequest; + + private final HashMap<String, ProviderStats> mStats = new HashMap<String, ProviderStats>(); + + public FusionEngine(Context context, Looper looper) { + mContext = context; + mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + mNetworkLocation = new Location(""); + mNetworkLocation.setAccuracy(Float.MAX_VALUE); + mGpsLocation = new Location(""); + mGpsLocation.setAccuracy(Float.MAX_VALUE); + mLooper = looper; + + mStats.put(GPS, new ProviderStats()); + mStats.get(GPS).available = mLocationManager.isProviderEnabled(GPS); + mStats.put(NETWORK, new ProviderStats()); + mStats.get(NETWORK).available = mLocationManager.isProviderEnabled(NETWORK); + } + + public void init(Callback callback) { + Log.i(TAG, "engine started (" + mContext.getPackageName() + ")"); + mCallback = callback; + } + + /** + * Called to stop doing any work, and release all resources + * This can happen when a better fusion engine is installed + * in a different package, and this one is no longer needed. + * Called on mLooper thread + */ + public void deinit() { + mRequest = null; + disable(); + Log.i(TAG, "engine stopped (" + mContext.getPackageName() + ")"); + } + + private boolean isAvailable() { + return mStats.get(GPS).available || mStats.get(NETWORK).available; + } + + /** Called on mLooper thread */ + public void enable() { + mEnabled = true; + updateRequirements(); + } + + /** Called on mLooper thread */ + public void disable() { + mEnabled = false; + updateRequirements(); + } + + /** Called on mLooper thread */ + public void setRequirements(ProviderRequestUnbundled request, WorkSource source) { + mRequest = request; + mEnabled = true; + updateRequirements(); + } + + private static class ProviderStats { + public boolean available; + public boolean requested; + public long requestTime; + public long minTime; + public long lastRequestTtff; + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append(available ? "AVAILABLE" : "UNAVAILABLE"); + s.append(requested ? " REQUESTED" : " ---"); + return s.toString(); + } + } + + private void enableProvider(String name, long minTime) { + ProviderStats stats = mStats.get(name); + + if (!stats.requested) { + stats.requestTime = SystemClock.elapsedRealtime(); + stats.requested = true; + stats.minTime = minTime; + mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper); + } else if (stats.minTime != minTime) { + stats.minTime = minTime; + mLocationManager.requestLocationUpdates(name, minTime, 0, this, mLooper); + } + } + + private void disableProvider(String name) { + ProviderStats stats = mStats.get(name); + + if (stats.requested) { + stats.requested = false; + mLocationManager.removeUpdates(this); //TODO GLOBAL + } + } + + private void updateRequirements() { + if (mEnabled == false || mRequest == null) { + mRequest = null; + disableProvider(NETWORK); + disableProvider(GPS); + return; + } + + ProviderStats gpsStats = mStats.get(GPS); + ProviderStats networkStats = mStats.get(NETWORK); + + long networkInterval = Long.MAX_VALUE; + long gpsInterval = Long.MAX_VALUE; + for (LocationRequest request : mRequest.getLocationRequests()) { + switch (request.getQuality()) { + case LocationRequest.ACCURACY_FINE: + case LocationRequest.POWER_HIGH: + if (request.getInterval() < gpsInterval) { + gpsInterval = request.getInterval(); + } + if (request.getInterval() < networkInterval) { + networkInterval = request.getInterval(); + } + break; + case LocationRequest.ACCURACY_BLOCK: + case LocationRequest.ACCURACY_CITY: + case LocationRequest.POWER_LOW: + if (request.getInterval() < networkInterval) { + networkInterval = request.getInterval(); + } + break; + } + } + + if (gpsInterval < Long.MAX_VALUE) { + enableProvider(GPS, gpsInterval); + } else { + disableProvider(GPS); + } + if (networkInterval < Long.MAX_VALUE) { + enableProvider(NETWORK, networkInterval); + } else { + disableProvider(NETWORK); + } + } + + private static double weighAccuracy(Location loc) { + double accuracy = loc.getAccuracy(); + return Math.exp(-accuracy * ACCURACY_DECAY_CONSTANT_M); + } + + private static double weighAge(Location loc) { + long ageSeconds = SystemClock.elapsedRealtimeNano() - loc.getElapsedRealtimeNano(); + ageSeconds /= 1000000000L; + if (ageSeconds < 0) ageSeconds = 0; + return Math.exp(-ageSeconds * AGE_DECAY_CONSTANT_S); + } + + private double weigh(double gps, double network) { + return (gps * mGpsWeight) + (network * mNetworkWeight); + } + + private double weigh(double gps, double network, double wrapMin, double wrapMax) { + // apply aliasing + double wrapWidth = wrapMax - wrapMin; + if (gps - network > wrapWidth / 2) network += wrapWidth; + else if (network - gps > wrapWidth / 2) gps += wrapWidth; + + double result = weigh(gps, network); + + // remove aliasing + if (result > wrapMax) result -= wrapWidth; + return result; + } + + private void updateFusedLocation() { + // naive fusion + mNetworkWeight = weighAccuracy(mNetworkLocation) * weighAge(mNetworkLocation); + mGpsWeight = weighAccuracy(mGpsLocation) * weighAge(mGpsLocation); + // scale mNetworkWeight and mGpsWeight so that they add to 1 + double totalWeight = mNetworkWeight + mGpsWeight; + mNetworkWeight /= totalWeight; + mGpsWeight /= totalWeight; + + Location fused = new Location(LocationManager.FUSED_PROVIDER); + // fuse lat/long + // assumes the two locations are close enough that earth curvature doesn't matter + fused.setLatitude(weigh(mGpsLocation.getLatitude(), mNetworkLocation.getLatitude())); + fused.setLongitude(weigh(mGpsLocation.getLongitude(), mNetworkLocation.getLongitude(), + -180.0, 180.0)); + + // fused accuracy + //TODO: use some real math instead of this crude fusion + // one suggestion is to fuse in a quadratic manner, eg + // sqrt(weigh(gpsAcc^2, netAcc^2)). + // another direction to explore is to consider the difference in the 2 + // locations. If the component locations overlap, the fused accuracy is + // better than the component accuracies. If they are far apart, + // the fused accuracy is much worse. + fused.setAccuracy((float)weigh(mGpsLocation.getAccuracy(), mNetworkLocation.getAccuracy())); + + // fused time - now + fused.setTime(System.currentTimeMillis()); + fused.setElapsedRealtimeNano(SystemClock.elapsedRealtimeNano()); + + // fuse altitude + if (mGpsLocation.hasAltitude() && !mNetworkLocation.hasAltitude() && + mGpsWeight > WEIGHT_THRESHOLD) { + fused.setAltitude(mGpsLocation.getAltitude()); // use GPS + } else if (!mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude() && + mNetworkWeight > WEIGHT_THRESHOLD) { + fused.setAltitude(mNetworkLocation.getAltitude()); // use Network + } else if (mGpsLocation.hasAltitude() && mNetworkLocation.hasAltitude()) { + fused.setAltitude(weigh(mGpsLocation.getAltitude(), mNetworkLocation.getAltitude())); + } + + // fuse speed + if (mGpsLocation.hasSpeed() && !mNetworkLocation.hasSpeed() && + mGpsWeight > WEIGHT_THRESHOLD) { + fused.setSpeed(mGpsLocation.getSpeed()); // use GPS if its not too old + } else if (!mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed() && + mNetworkWeight > WEIGHT_THRESHOLD) { + fused.setSpeed(mNetworkLocation.getSpeed()); // use Network + } else if (mGpsLocation.hasSpeed() && mNetworkLocation.hasSpeed()) { + fused.setSpeed((float)weigh(mGpsLocation.getSpeed(), mNetworkLocation.getSpeed())); + } + + // fuse bearing + if (mGpsLocation.hasBearing() && !mNetworkLocation.hasBearing() && + mGpsWeight > WEIGHT_THRESHOLD) { + fused.setBearing(mGpsLocation.getBearing()); // use GPS if its not too old + } else if (!mGpsLocation.hasBearing() && mNetworkLocation.hasBearing() && + mNetworkWeight > WEIGHT_THRESHOLD) { + fused.setBearing(mNetworkLocation.getBearing()); // use Network + } else if (mGpsLocation.hasBearing() && mNetworkLocation.hasBearing()) { + fused.setBearing((float)weigh(mGpsLocation.getBearing(), mNetworkLocation.getBearing(), + 0.0, 360.0)); + } + + mFusedLocation = fused; + + mCallback.reportLocation(mFusedLocation); + } + + /** Called on mLooper thread */ + @Override + public void onLocationChanged(Location location) { + if (GPS.equals(location.getProvider())) { + mGpsLocation = location; + updateFusedLocation(); + } else if (NETWORK.equals(location.getProvider())) { + mNetworkLocation = location; + updateFusedLocation(); + } + } + + /** Called on mLooper thread */ + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { } + + /** Called on mLooper thread */ + @Override + public void onProviderEnabled(String provider) { + ProviderStats stats = mStats.get(provider); + if (stats == null) return; + + stats.available = true; + } + + /** Called on mLooper thread */ + @Override + public void onProviderDisabled(String provider) { + ProviderStats stats = mStats.get(provider); + if (stats == null) return; + + stats.available = false; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + StringBuilder s = new StringBuilder(); + s.append("mEnabled=" + mEnabled).append(' ').append(mRequest).append('\n'); + s.append("fused=").append(mFusedLocation).append('\n'); + s.append(String.format("gps %.3f %s\n", mGpsWeight, mGpsLocation)); + s.append(" ").append(mStats.get(GPS)).append('\n'); + s.append(String.format("net %.3f %s\n", mNetworkWeight, mNetworkLocation)); + s.append(" ").append(mStats.get(NETWORK)).append('\n'); + pw.append(s); + } +} diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 198ba8b..a6c3860 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -16,21 +16,21 @@ package com.android.server; -import android.app.Activity; import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.ContentQueryMap; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.Cursor; import android.location.Address; import android.location.Criteria; import android.location.GeocoderParams; +import android.location.Geofence; import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; import android.location.ILocationListener; @@ -39,26 +39,28 @@ import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; +import android.location.LocationRequest; import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; import android.os.WorkSource; import android.provider.Settings; import android.provider.Settings.NameValueTable; import android.util.Log; import android.util.Slog; -import android.util.PrintWriterPrinter; import com.android.internal.content.PackageMonitor; - +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; import com.android.server.location.GeocoderProxy; import com.android.server.location.GeofenceManager; import com.android.server.location.GpsLocationProvider; @@ -70,8 +72,7 @@ import com.android.server.location.PassiveProvider; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -83,135 +84,273 @@ import java.util.Set; /** * The service class that manages LocationProviders and issues location * updates and alerts. - * - * {@hide} */ -public class LocationManagerService extends ILocationManager.Stub implements Runnable { +public class LocationManagerService extends ILocationManager.Stub implements Observer, Runnable { private static final String TAG = "LocationManagerService"; - private static final boolean LOCAL_LOGV = false; + public static final boolean D = false; + + private static final String WAKELOCK_KEY = TAG; + private static final String THREAD_NAME = TAG; private static final String ACCESS_FINE_LOCATION = - android.Manifest.permission.ACCESS_FINE_LOCATION; + android.Manifest.permission.ACCESS_FINE_LOCATION; private static final String ACCESS_COARSE_LOCATION = - android.Manifest.permission.ACCESS_COARSE_LOCATION; + android.Manifest.permission.ACCESS_COARSE_LOCATION; private static final String ACCESS_MOCK_LOCATION = - android.Manifest.permission.ACCESS_MOCK_LOCATION; + android.Manifest.permission.ACCESS_MOCK_LOCATION; private static final String ACCESS_LOCATION_EXTRA_COMMANDS = - android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; + android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; private static final String INSTALL_LOCATION_PROVIDER = - android.Manifest.permission.INSTALL_LOCATION_PROVIDER; + android.Manifest.permission.INSTALL_LOCATION_PROVIDER; + + private static final String NETWORK_LOCATION_SERVICE_ACTION = + "com.android.location.service.v2.NetworkLocationProvider"; + private static final String FUSED_LOCATION_SERVICE_ACTION = + "com.android.location.service.FusedLocationProvider"; + + private static final int MSG_LOCATION_CHANGED = 1; + + // Accuracy in meters above which a location is considered coarse + private static final double COARSE_ACCURACY_M = 100.0; + private static final String EXTRA_COARSE_LOCATION = "coarseLocation"; + + private static final int APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111000; + + /** + * Maximum latitude of 1 meter from the pole. + * This keeps cosine(MAX_LATITUDE) to a non-zero value; + */ + private static final double MAX_LATITUDE = + 90.0 - (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR); // Location Providers may sometimes deliver location updates // slightly faster that requested - provide grace period so // we don't unnecessarily filter events that are otherwise on // time - private static final int MAX_PROVIDER_SCHEDULING_JITTER = 100; + private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100; - // Set of providers that are explicitly enabled - private final Set<String> mEnabledProviders = new HashSet<String>(); + private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest(); - // Set of providers that are explicitly disabled - private final Set<String> mDisabledProviders = new HashSet<String>(); - - // Locations, status values, and extras for mock providers - private final HashMap<String,MockProvider> mMockProviders = new HashMap<String,MockProvider>(); + private final Context mContext; - private static boolean sProvidersLoaded = false; + // used internally for synchronization + private final Object mLock = new Object(); - private final Context mContext; - private PackageManager mPackageManager; // final after initialize() - private String mNetworkLocationProviderPackageName; // only used on handler thread - private String mGeocodeProviderPackageName; // only used on handler thread + // --- fields below are final after init() --- + private GeofenceManager mGeofenceManager; + private PowerManager.WakeLock mWakeLock; + private PackageManager mPackageManager; private GeocoderProxy mGeocodeProvider; private IGpsStatusProvider mGpsStatusProvider; private INetInitiatedListener mNetInitiatedListener; private LocationWorkerHandler mLocationHandler; + // track the passive provider for some special cases + private PassiveProvider mPassiveProvider; - // Cache the real providers for use in addTestProvider() and removeTestProvider() - LocationProviderProxy mNetworkLocationProvider; - LocationProviderInterface mGpsLocationProvider; + // --- fields below are protected by mWakeLock --- + private int mPendingBroadcasts; - // Handler messages - private static final int MESSAGE_LOCATION_CHANGED = 1; - private static final int MESSAGE_PACKAGE_UPDATED = 2; + // --- fields below are protected by mLock --- + // Set of providers that are explicitly enabled + private final Set<String> mEnabledProviders = new HashSet<String>(); - // wakelock variables - private final static String WAKELOCK_KEY = "LocationManagerService"; - private PowerManager.WakeLock mWakeLock = null; - private int mPendingBroadcasts; + // Set of providers that are explicitly disabled + private final Set<String> mDisabledProviders = new HashSet<String>(); - /** - * List of all receivers. - */ - private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>(); + // Mock (test) providers + private final HashMap<String, MockProvider> mMockProviders = + new HashMap<String, MockProvider>(); + // all receivers + private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>(); - /** - * List of location providers. - */ + // currently installed providers (with mocks replacing real providers) private final ArrayList<LocationProviderInterface> mProviders = - new ArrayList<LocationProviderInterface>(); - private final HashMap<String, LocationProviderInterface> mProvidersByName - = new HashMap<String, LocationProviderInterface>(); + new ArrayList<LocationProviderInterface>(); - /** - * Object used internally for synchronization - */ - private final Object mLock = new Object(); + // real providers, saved here when mocked out + private final HashMap<String, LocationProviderInterface> mRealProviders = + new HashMap<String, LocationProviderInterface>(); - /** - * Mapping from provider name to all its UpdateRecords - */ - private final HashMap<String,ArrayList<UpdateRecord>> mRecordsByProvider = - new HashMap<String,ArrayList<UpdateRecord>>(); + // mapping from provider name to provider + private final HashMap<String, LocationProviderInterface> mProvidersByName = + new HashMap<String, LocationProviderInterface>(); - /** - * Temporary filled in when computing min time for a provider. Access is - * protected by global lock mLock. - */ - private final WorkSource mTmpWorkSource = new WorkSource(); + // mapping from provider name to all its UpdateRecords + private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider = + new HashMap<String, ArrayList<UpdateRecord>>(); + + // mapping from provider name to last known location + private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>(); + + // all providers that operate over proxy, for authorizing incoming location + private final ArrayList<LocationProviderProxy> mProxyProviders = + new ArrayList<LocationProviderProxy>(); + + public LocationManagerService(Context context) { + super(); + mContext = context; + + if (D) Log.d(TAG, "Constructed"); - GeofenceManager mGeofenceManager; + // most startup is deferred until systemReady() + } - // Last known location for each provider - private HashMap<String,Location> mLastKnownLocation = - new HashMap<String,Location>(); + public void systemReady() { + Thread thread = new Thread(null, this, THREAD_NAME); + thread.start(); + } - private int mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + Looper.prepare(); + mLocationHandler = new LocationWorkerHandler(); + init(); + Looper.loop(); + } - // for Settings change notification - private ContentQueryMap mSettings; + private void init() { + if (D) Log.d(TAG, "init()"); + + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); + mPackageManager = mContext.getPackageManager(); + + synchronized (mLock) { + loadProvidersLocked(); + } + mGeofenceManager = new GeofenceManager(mContext); + + // Register for Network (Wifi or Mobile) updates + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + + // listen for settings changes + ContentResolver resolver = mContext.getContentResolver(); + Cursor settingsCursor = resolver.query(Settings.Secure.CONTENT_URI, null, + "(" + NameValueTable.NAME + "=?)", + new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED}, null); + ContentQueryMap query = new ContentQueryMap(settingsCursor, NameValueTable.NAME, true, + mLocationHandler); + settingsCursor.close(); + query.addObserver(this); + mPackageMonitor.register(mContext, Looper.myLooper(), true); + } + + private void loadProvidersLocked() { + if (GpsLocationProvider.isSupported()) { + // Create a gps location provider + GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); + mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); + mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); + addProviderLocked(gpsProvider); + mRealProviders.put(LocationManager.GPS_PROVIDER, gpsProvider); + } + + // create a passive location provider, which is always enabled + PassiveProvider passiveProvider = new PassiveProvider(this); + addProviderLocked(passiveProvider); + mEnabledProviders.add(passiveProvider.getName()); + mPassiveProvider = passiveProvider; + + /* + Load package name(s) containing location provider support. + These packages can contain services implementing location providers: + Geocoder Provider, Network Location Provider, and + Fused Location Provider. They will each be searched for + service components implementing these providers. + The location framework also has support for installation + of new location providers at run-time. The new package does not + have to be explicitly listed here, however it must have a signature + that matches the signature of at least one package on this list. + */ + Resources resources = mContext.getResources(); + ArrayList<String> providerPackageNames = new ArrayList<String>(); + String[] pkgs1 = resources.getStringArray( + com.android.internal.R.array.config_locationProviderPackageNames); + String[] pkgs2 = resources.getStringArray( + com.android.internal.R.array.config_overlay_locationProviderPackageNames); + if (D) Log.d(TAG, "built-in location providers: " + Arrays.toString(pkgs1)); + if (D) Log.d(TAG, "overlay location providers: " + Arrays.toString(pkgs2)); + if (pkgs1 != null) providerPackageNames.addAll(Arrays.asList(pkgs1)); + if (pkgs2 != null) providerPackageNames.addAll(Arrays.asList(pkgs2)); + + // bind to network provider + LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( + mContext, + LocationManager.NETWORK_PROVIDER, + NETWORK_LOCATION_SERVICE_ACTION, + providerPackageNames, mLocationHandler); + if (networkProvider != null) { + mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider); + mProxyProviders.add(networkProvider); + addProviderLocked(networkProvider); + } else { + Slog.w(TAG, "no network location provider found"); + } + + // bind to fused provider + LocationProviderProxy fusedLocationProvider = LocationProviderProxy.createAndBind( + mContext, + LocationManager.FUSED_PROVIDER, + FUSED_LOCATION_SERVICE_ACTION, + providerPackageNames, mLocationHandler); + if (fusedLocationProvider != null) { + addProviderLocked(fusedLocationProvider); + mProxyProviders.add(fusedLocationProvider); + mEnabledProviders.add(fusedLocationProvider.getName()); + } else { + Slog.e(TAG, "no fused location provider found", + new IllegalStateException("Location service needs a fused location provider")); + } + + // bind to geocoder provider + mGeocodeProvider = GeocoderProxy.createAndBind(mContext, providerPackageNames); + if (mGeocodeProvider == null) { + Slog.e(TAG, "no geocoder provider found"); + } + + updateProvidersLocked(); + } /** * A wrapper class holding either an ILocationListener or a PendingIntent to receive * location updates. */ private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished { + final int mUid; // uid of receiver + final int mPid; // pid of receiver + final String mPackageName; // package name of receiver + final String mPermission; // best permission that receiver has + final ILocationListener mListener; final PendingIntent mPendingIntent; final Object mKey; + final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>(); int mPendingBroadcasts; - String mRequiredPermissions; - Receiver(ILocationListener listener) { + Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid, + String packageName) { mListener = listener; - mPendingIntent = null; - mKey = listener.asBinder(); - } - - Receiver(PendingIntent intent) { mPendingIntent = intent; - mListener = null; - mKey = intent; + if (listener != null) { + mKey = listener.asBinder(); + } else { + mKey = intent; + } + mPermission = checkPermission(); + mUid = uid; + mPid = pid; + mPackageName = packageName; } @Override public boolean equals(Object otherObj) { if (otherObj instanceof Receiver) { - return mKey.equals( - ((Receiver)otherObj).mKey); + return mKey.equals(((Receiver)otherObj).mKey); } return false; } @@ -223,18 +362,19 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public String toString() { - String result; + StringBuilder s = new StringBuilder(); + s.append("Reciever["); + s.append(Integer.toHexString(System.identityHashCode(this))); if (mListener != null) { - result = "Receiver{" - + Integer.toHexString(System.identityHashCode(this)) - + " Listener " + mKey + "}"; + s.append(" listener"); } else { - result = "Receiver{" - + Integer.toHexString(System.identityHashCode(this)) - + " Intent " + mKey + "}"; + s.append(" intent"); } - result += "mUpdateRecords: " + mUpdateRecords; - return result; + for (String p : mUpdateRecords.keySet()) { + s.append(" ").append(mUpdateRecords.get(p).toString()); + } + s.append("]"); + return s.toString(); } public boolean isListener() { @@ -275,7 +415,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -309,7 +449,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -347,7 +487,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -361,9 +501,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public void binderDied() { - if (LOCAL_LOGV) { - Slog.v(TAG, "Location listener died"); - } + if (D) Log.d(TAG, "Location listener died"); + synchronized (mLock) { removeUpdatesLocked(this); } @@ -416,200 +555,25 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private final class SettingsObserver implements Observer { - @Override - public void update(Observable o, Object arg) { - synchronized (mLock) { - updateProvidersLocked(); - } + /** Settings Observer callback */ + @Override + public void update(Observable o, Object arg) { + synchronized (mLock) { + updateProvidersLocked(); } } - private void addProvider(LocationProviderInterface provider) { + private void addProviderLocked(LocationProviderInterface provider) { mProviders.add(provider); mProvidersByName.put(provider.getName(), provider); } - private void removeProvider(LocationProviderInterface provider) { + private void removeProviderLocked(LocationProviderInterface provider) { + provider.disable(); mProviders.remove(provider); mProvidersByName.remove(provider.getName()); } - private void loadProviders() { - synchronized (mLock) { - if (sProvidersLoaded) { - return; - } - - // Load providers - loadProvidersLocked(); - sProvidersLoaded = true; - } - } - - private void loadProvidersLocked() { - try { - _loadProvidersLocked(); - } catch (Exception e) { - Slog.e(TAG, "Exception loading providers:", e); - } - } - - private void _loadProvidersLocked() { - // Attempt to load "real" providers first - if (GpsLocationProvider.isSupported()) { - // Create a gps location provider - GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); - mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); - mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); - addProvider(gpsProvider); - mGpsLocationProvider = gpsProvider; - } - - // create a passive location provider, which is always enabled - PassiveProvider passiveProvider = new PassiveProvider(this); - addProvider(passiveProvider); - mEnabledProviders.add(passiveProvider.getName()); - - // initialize external network location and geocoder services. - // The initial value of mNetworkLocationProviderPackageName and - // mGeocodeProviderPackageName is just used to determine what - // signatures future mNetworkLocationProviderPackageName and - // mGeocodeProviderPackageName packages must have. So alternate - // providers can be installed under a different package name - // so long as they have the same signature as the original - // provider packages. - if (mNetworkLocationProviderPackageName != null) { - String packageName = findBestPackage(LocationProviderProxy.SERVICE_ACTION, - mNetworkLocationProviderPackageName); - if (packageName != null) { - mNetworkLocationProvider = new LocationProviderProxy(mContext, - LocationManager.NETWORK_PROVIDER, - packageName, mLocationHandler); - mNetworkLocationProviderPackageName = packageName; - addProvider(mNetworkLocationProvider); - } - } - if (mGeocodeProviderPackageName != null) { - String packageName = findBestPackage(GeocoderProxy.SERVICE_ACTION, - mGeocodeProviderPackageName); - if (packageName != null) { - mGeocodeProvider = new GeocoderProxy(mContext, packageName); - mGeocodeProviderPackageName = packageName; - } - } - - updateProvidersLocked(); - } - - /** - * Pick the best (network location provider or geocode provider) package. - * The best package: - * - implements serviceIntentName - * - has signatures that match that of sigPackageName - * - has the highest version value in a meta-data field in the service component - */ - String findBestPackage(String serviceIntentName, String sigPackageName) { - Intent intent = new Intent(serviceIntentName); - List<ResolveInfo> infos = mPackageManager.queryIntentServices(intent, - PackageManager.GET_META_DATA); - if (infos == null) return null; - - int bestVersion = Integer.MIN_VALUE; - String bestPackage = null; - for (ResolveInfo info : infos) { - String packageName = info.serviceInfo.packageName; - // check signature - if (mPackageManager.checkSignatures(packageName, sigPackageName) != - PackageManager.SIGNATURE_MATCH) { - Slog.w(TAG, packageName + " implements " + serviceIntentName + - " but its signatures don't match those in " + sigPackageName + - ", ignoring"); - continue; - } - // read version - int version = 0; - if (info.serviceInfo.metaData != null) { - version = info.serviceInfo.metaData.getInt("version", 0); - } - if (LOCAL_LOGV) Slog.v(TAG, packageName + " implements " + serviceIntentName + - " with version " + version); - if (version > bestVersion) { - bestVersion = version; - bestPackage = packageName; - } - } - - return bestPackage; - } - - /** - * @param context the context that the LocationManagerService runs in - */ - public LocationManagerService(Context context) { - super(); - mContext = context; - Resources resources = context.getResources(); - - mNetworkLocationProviderPackageName = resources.getString( - com.android.internal.R.string.config_networkLocationProviderPackageName); - mGeocodeProviderPackageName = resources.getString( - com.android.internal.R.string.config_geocodeProviderPackageName); - - mPackageMonitor.register(context, null, true); - - if (LOCAL_LOGV) { - Slog.v(TAG, "Constructed LocationManager Service"); - } - } - - void systemReady() { - // we defer starting up the service until the system is ready - Thread thread = new Thread(null, this, "LocationManagerService"); - thread.start(); - } - - private void initialize() { - // Create a wake lock, needs to be done before calling loadProviders() below - PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); - mPackageManager = mContext.getPackageManager(); - - // Load providers - loadProviders(); - - // Register for Network (Wifi or Mobile) updates - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - // Register for Package Manager updates - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); - intentFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); - mContext.registerReceiver(mBroadcastReceiver, intentFilter); - IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mBroadcastReceiver, sdFilter); - - // listen for settings changes - ContentResolver resolver = mContext.getContentResolver(); - Cursor settingsCursor = resolver.query(Settings.Secure.CONTENT_URI, null, - "(" + NameValueTable.NAME + "=?)", - new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED}, - null); - mSettings = new ContentQueryMap(settingsCursor, NameValueTable.NAME, true, mLocationHandler); - SettingsObserver settingsObserver = new SettingsObserver(); - mSettings.addObserver(settingsObserver); - } - - @Override - public void run() - { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - Looper.prepare(); - mLocationHandler = new LocationWorkerHandler(); - initialize(); - mGeofenceManager = new GeofenceManager(mContext); - Looper.loop(); - } private boolean isAllowedBySettingsLocked(String provider) { if (mEnabledProviders.contains(provider)) { @@ -624,324 +588,131 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return Settings.Secure.isLocationProviderEnabled(resolver, provider); } - private String checkPermissionsSafe(String provider, String lastPermission) { - if (LocationManager.GPS_PROVIDER.equals(provider) - || LocationManager.PASSIVE_PROVIDER.equals(provider)) { - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Provider " + provider - + " requires ACCESS_FINE_LOCATION permission"); - } - return ACCESS_FINE_LOCATION; - } - - // Assume any other provider requires the coarse or fine permission. - if (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { - return ACCESS_FINE_LOCATION.equals(lastPermission) - ? lastPermission : ACCESS_COARSE_LOCATION; - } - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { + /** + * Throw SecurityException if caller has neither COARSE or FINE. + * Otherwise, return the best permission. + */ + private String checkPermission() { + if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) == + PackageManager.PERMISSION_GRANTED) { return ACCESS_FINE_LOCATION; + } else if (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) == + PackageManager.PERMISSION_GRANTED) { + return ACCESS_COARSE_LOCATION; } - throw new SecurityException("Provider " + provider - + " requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission"); - } - - private boolean isAllowedProviderSafe(String provider) { - if ((LocationManager.GPS_PROVIDER.equals(provider) - || LocationManager.PASSIVE_PROVIDER.equals(provider)) - && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED)) { - return false; - } - if (LocationManager.NETWORK_PROVIDER.equals(provider) - && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) - && (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) - != PackageManager.PERMISSION_GRANTED)) { - return false; - } - - return true; + throw new SecurityException("Location requires either ACCESS_COARSE_LOCATION or" + + "ACCESS_FINE_LOCATION permission"); } + /** + * Returns all providers by name, including passive, but excluding + * fused. + */ @Override public List<String> getAllProviders() { - try { - synchronized (mLock) { - return _getAllProvidersLocked(); + checkPermission(); + + ArrayList<String> out; + synchronized (mLock) { + out = new ArrayList<String>(mProviders.size()); + for (LocationProviderInterface provider : mProviders) { + String name = provider.getName(); + if (LocationManager.FUSED_PROVIDER.equals(name)) { + continue; + } + out.add(name); } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getAllProviders got exception:", e); - return null; } - } - private List<String> _getAllProvidersLocked() { - if (LOCAL_LOGV) { - Slog.v(TAG, "getAllProviders"); - } - ArrayList<String> out = new ArrayList<String>(mProviders.size()); - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - out.add(p.getName()); - } + if (D) Log.d(TAG, "getAllProviders()=" + out); return out; } + /** + * Return all providers by name, that match criteria and are optionally + * enabled. + * Can return passive provider, but never returns fused provider. + */ @Override public List<String> getProviders(Criteria criteria, boolean enabledOnly) { - try { - synchronized (mLock) { - return _getProvidersLocked(criteria, enabledOnly); - } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getProviders got exception:", e); - return null; - } - } + checkPermission(); - private List<String> _getProvidersLocked(Criteria criteria, boolean enabledOnly) { - if (LOCAL_LOGV) { - Slog.v(TAG, "getProviders"); - } - ArrayList<String> out = new ArrayList<String>(mProviders.size()); - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - String name = p.getName(); - if (isAllowedProviderSafe(name)) { + ArrayList<String> out; + synchronized (mLock) { + out = new ArrayList<String>(mProviders.size()); + for (LocationProviderInterface provider : mProviders) { + String name = provider.getName(); + if (LocationManager.FUSED_PROVIDER.equals(name)) { + continue; + } if (enabledOnly && !isAllowedBySettingsLocked(name)) { continue; } - if (criteria != null && !p.meetsCriteria(criteria)) { + if (criteria != null && !LocationProvider.propertiesMeetCriteria( + name, provider.getProperties(), criteria)) { continue; } out.add(name); } } - return out; - } - /** - * 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 class LpPowerComparator implements Comparator<LocationProviderInterface> { - @Override - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - // Smaller is better - return (l1.getPowerRequirement() - l2.getPowerRequirement()); - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (l1.getPowerRequirement() == l2.getPowerRequirement()); - } - } - - private class LpAccuracyComparator implements Comparator<LocationProviderInterface> { - @Override - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - // Smaller is better - return (l1.getAccuracy() - l2.getAccuracy()); - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (l1.getAccuracy() == l2.getAccuracy()); - } - } - - private class LpCapabilityComparator implements Comparator<LocationProviderInterface> { - - private static final int ALTITUDE_SCORE = 4; - private static final int BEARING_SCORE = 4; - private static final int SPEED_SCORE = 4; - - private int score(LocationProviderInterface p) { - return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) + - (p.supportsBearing() ? BEARING_SCORE : 0) + - (p.supportsSpeed() ? SPEED_SCORE : 0); - } - - @Override - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - return (score(l2) - score(l1)); // Bigger is better - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (score(l1) == score(l2)); - } - } - - private LocationProviderInterface best(List<String> providerNames) { - ArrayList<LocationProviderInterface> providers; - synchronized (mLock) { - providers = new ArrayList<LocationProviderInterface>(providerNames.size()); - for (String name : providerNames) { - providers.add(mProvidersByName.get(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; - - ArrayList<LocationProviderInterface> tmp = new ArrayList<LocationProviderInterface>(); - 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); - } - - ArrayList<LocationProviderInterface> tmp2 = new ArrayList<LocationProviderInterface>(); - 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); + if (D) Log.d(TAG, "getProviders()=" + out); + return out; } /** - * 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 + * Return the name of the best provider given a Criteria object. + * This method has been deprecated from the public API, + * and the whole LoactionProvider (including #meetsCriteria) + * has been deprecated as well. So this method now uses + * some simplified logic. */ @Override public String getBestProvider(Criteria criteria, boolean enabledOnly) { - List<String> goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } + String result = null; + checkPermission(); - // 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 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(); + List<String> providers = getProviders(criteria, enabledOnly); + if (providers.size() < 1) { + result = pickBest(providers); + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return result; } - - // Remove bearing requirement - criteria.setBearingRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); + providers = getProviders(null, enabledOnly); + if (providers.size() >= 1) { + result = pickBest(providers); + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return result; } - // Remove speed requirement - criteria.setSpeedRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return null; + } - // Remove altitude requirement - criteria.setAltitudeRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); + private String pickBest(List<String> providers) { + if (providers.contains(LocationManager.NETWORK_PROVIDER)) { + return LocationManager.NETWORK_PROVIDER; + } else if (providers.contains(LocationManager.GPS_PROVIDER)) { + return LocationManager.GPS_PROVIDER; + } else { + return providers.get(0); } - - return null; } @Override public boolean providerMeetsCriteria(String provider, Criteria criteria) { + checkPermission(); + LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { throw new IllegalArgumentException("provider=" + provider); } - return p.meetsCriteria(criteria); + + boolean result = LocationProvider.propertiesMeetCriteria( + p.getName(), p.getProperties(), criteria); + if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result); + return result; } private void updateProvidersLocked() { @@ -968,16 +739,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run int listeners = 0; LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return; - } + if (p == null) return; ArrayList<Receiver> deadReceivers = null; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); if (records != null) { final int N = records.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { UpdateRecord record = records.get(i); // Sends a notification message to the receiver if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) { @@ -991,7 +760,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } if (deadReceivers != null) { - for (int i=deadReceivers.size()-1; i>=0; i--) { + for (int i = deadReceivers.size() - 1; i >= 0; i--) { removeUpdatesLocked(deadReceivers.get(i)); } } @@ -999,59 +768,70 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (enabled) { p.enable(); if (listeners > 0) { - p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource); - p.enableLocationTracking(true); + applyRequirementsLocked(provider); } } else { - p.enableLocationTracking(false); p.disable(); } } - private long getMinTimeLocked(String provider) { - long minTime = Long.MAX_VALUE; + private void applyRequirementsLocked(String provider) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) return; + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - mTmpWorkSource.clear(); + WorkSource worksource = new WorkSource(); + ProviderRequest providerRequest = new ProviderRequest(); + if (records != null) { - for (int i=records.size()-1; i>=0; i--) { - UpdateRecord ur = records.get(i); - long curTime = ur.mMinTime; - if (curTime < minTime) { - minTime = curTime; + for (UpdateRecord record : records) { + LocationRequest locationRequest = record.mRequest; + + if (providerRequest.locationRequests == null) { + providerRequest.locationRequests = new ArrayList<LocationRequest>(); + } + + providerRequest.locationRequests.add(locationRequest); + if (locationRequest.getInterval() < providerRequest.interval) { + providerRequest.reportLocation = true; + providerRequest.interval = locationRequest.getInterval(); } } - long inclTime = (minTime*3)/2; - for (int i=records.size()-1; i>=0; i--) { - UpdateRecord ur = records.get(i); - if (ur.mMinTime <= inclTime) { - mTmpWorkSource.add(ur.mUid); + + if (providerRequest.reportLocation) { + // calculate who to blame for power + // This is somewhat arbitrary. We pick a threshold interval + // that is slightly higher that the minimum interval, and + // spread the blame across all applications with a request + // under that threshold. + long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; + for (UpdateRecord record : records) { + LocationRequest locationRequest = record.mRequest; + if (locationRequest.getInterval() <= thresholdInterval) { + worksource.add(record.mReceiver.mUid); + } } } } - return minTime; + + if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest); + p.setRequest(providerRequest, worksource); } private class UpdateRecord { final String mProvider; + final LocationRequest mRequest; final Receiver mReceiver; - final long mMinTime; - final float mMinDistance; - final boolean mSingleShot; - final int mUid; Location mLastFixBroadcast; long mLastStatusBroadcast; /** * Note: must be constructed with lock held. */ - UpdateRecord(String provider, long minTime, float minDistance, boolean singleShot, - Receiver receiver, int uid) { + UpdateRecord(String provider, LocationRequest request, Receiver receiver) { mProvider = provider; + mRequest = request; mReceiver = receiver; - mMinTime = minTime; - mMinDistance = minDistance; - mSingleShot = singleShot; - mUid = uid; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); if (records == null) { @@ -1067,45 +847,49 @@ public class LocationManagerService extends ILocationManager.Stub implements Run * Method to be called when a record will no longer be used. Calling this multiple times * must have the same effect as calling it once. */ - void disposeLocked() { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(this.mProvider); - if (records != null) { - records.remove(this); + void disposeLocked(boolean removeReceiver) { + // remove from mRecordsByProvider + ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider); + if (globalRecords != null) { + globalRecords.remove(this); + } + + if (!removeReceiver) return; // the caller will handle the rest + + // remove from Receiver#mUpdateRecords + HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords; + if (receiverRecords != null) { + receiverRecords.remove(this.mProvider); + + // and also remove the Receiver if it has no more update records + if (removeReceiver && receiverRecords.size() == 0) { + removeUpdatesLocked(mReceiver); + } } } @Override public String toString() { - return "UpdateRecord{" - + Integer.toHexString(System.identityHashCode(this)) - + " mProvider: " + mProvider + " mUid: " + mUid + "}"; - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + this); - pw.println(prefix + "mProvider=" + mProvider + " mReceiver=" + mReceiver); - pw.println(prefix + "mMinTime=" + mMinTime + " mMinDistance=" + mMinDistance); - pw.println(prefix + "mSingleShot=" + mSingleShot); - pw.println(prefix + "mUid=" + mUid); - pw.println(prefix + "mLastFixBroadcast:"); - if (mLastFixBroadcast != null) { - mLastFixBroadcast.dump(new PrintWriterPrinter(pw), prefix + " "); - } - pw.println(prefix + "mLastStatusBroadcast=" + mLastStatusBroadcast); + StringBuilder s = new StringBuilder(); + s.append("UpdateRecord["); + s.append(mProvider); + s.append(' ').append(mReceiver.mPackageName).append('('); + s.append(mReceiver.mUid).append(')'); + s.append(' ').append(mRequest); + s.append(']'); + return s.toString(); } } - private Receiver getReceiver(ILocationListener listener) { + private Receiver getReceiver(ILocationListener listener, int pid, int uid, String packageName) { IBinder binder = listener.asBinder(); Receiver receiver = mReceivers.get(binder); if (receiver == null) { - receiver = new Receiver(listener); + receiver = new Receiver(listener, null, pid, uid, packageName); mReceivers.put(binder, receiver); try { - if (receiver.isListener()) { - receiver.getListener().asBinder().linkToDeath(receiver, 0); - } + receiver.getListener().asBinder().linkToDeath(receiver, 0); } catch (RemoteException e) { Slog.e(TAG, "linkToDeath failed:", e); return null; @@ -1114,58 +898,29 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return receiver; } - private Receiver getReceiver(PendingIntent intent) { + private Receiver getReceiver(PendingIntent intent, int pid, int uid, String packageName) { Receiver receiver = mReceivers.get(intent); if (receiver == null) { - receiver = new Receiver(intent); + receiver = new Receiver(null, intent, pid, uid, packageName); mReceivers.put(intent, receiver); } return receiver; } - private boolean providerHasListener(String provider, int uid, Receiver excludedReceiver) { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - if (records != null) { - for (int i = records.size() - 1; i >= 0; i--) { - UpdateRecord record = records.get(i); - if (record.mUid == uid && record.mReceiver != excludedReceiver) { - return true; - } - } - } - return false; - } + private String checkPermissionAndRequest(LocationRequest request) { + String perm = checkPermission(); - @Override - public void requestLocationUpdates(String provider, Criteria criteria, - long minTime, float minDistance, boolean singleShot, ILocationListener listener) { - if (criteria != null) { - // FIXME - should we consider using multiple providers simultaneously - // rather than only the best one? - // Should we do anything different for single shot fixes? - provider = getBestProvider(criteria, true); - if (provider == null) { - throw new IllegalArgumentException("no providers found for criteria"); - } - } - try { - synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, - getReceiver(listener)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "requestUpdates got exception:", e); + if (ACCESS_COARSE_LOCATION.equals(perm)) { + request.applyCoarsePermissionRestrictions(); } + return perm; } - void validatePackageName(int uid, String packageName) { + private void checkPackageName(String packageName) { if (packageName == null) { - throw new SecurityException("packageName cannot be null"); + throw new SecurityException("invalid package name: " + packageName); } + int uid = Binder.getCallingUid(); String[] packages = mPackageManager.getPackagesForUid(uid); if (packages == null) { throw new SecurityException("invalid UID " + uid); @@ -1173,202 +928,188 @@ public class LocationManagerService extends ILocationManager.Stub implements Run for (String pkg : packages) { if (packageName.equals(pkg)) return; } - throw new SecurityException("invalid package name"); + throw new SecurityException("invalid package name: " + packageName); } - void validatePendingIntent(PendingIntent intent) { - if (intent.isTargetedToPackage()) { - return; + private void checkPendingIntent(PendingIntent intent) { + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + intent); } - Slog.i(TAG, "Given Intent does not require a specific package: " - + intent); - // XXX we should really throw a security exception, if the caller's - // targetSdkVersion is high enough. - //throw new SecurityException("Given Intent does not require a specific package: " - // + intent); } - @Override - public void requestLocationUpdatesPI(String provider, Criteria criteria, - long minTime, float minDistance, boolean singleShot, PendingIntent intent) { - validatePendingIntent(intent); - if (criteria != null) { - // FIXME - should we consider using multiple providers simultaneously - // rather than only the best one? - // Should we do anything different for single shot fixes? - provider = getBestProvider(criteria, true); - if (provider == null) { - throw new IllegalArgumentException("no providers found for criteria"); - } - } - try { - synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, - getReceiver(intent)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "requestUpdates got exception:", e); + private Receiver checkListenerOrIntent(ILocationListener listener, PendingIntent intent, + int pid, int uid, String packageName) { + if (intent == null && listener == null) { + throw new IllegalArgumentException("need eiter listener or intent"); + } else if (intent != null && listener != null) { + throw new IllegalArgumentException("cannot register both listener and intent"); + } else if (intent != null) { + checkPendingIntent(intent); + return getReceiver(intent, pid, uid, packageName); + } else { + return getReceiver(listener, pid, uid, packageName); } } - private void requestLocationUpdatesLocked(String provider, long minTime, float minDistance, - boolean singleShot, Receiver receiver) { + @Override + public void requestLocationUpdates(LocationRequest request, ILocationListener listener, + PendingIntent intent, String packageName) { + if (request == null) request = DEFAULT_LOCATION_REQUEST; + checkPackageName(packageName); + checkPermissionAndRequest(request); - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - throw new IllegalArgumentException("requested provider " + provider + - " doesn't exisit"); - } - receiver.mRequiredPermissions = checkPermissionsSafe(provider, - receiver.mRequiredPermissions); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + Receiver recevier = checkListenerOrIntent(listener, intent, pid, uid, packageName); - // so wakelock calls will succeed - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); - boolean newUid = !providerHasListener(provider, callingUid, null); + // so wakelock calls will succeed (not totally sure this is still needed) long identity = Binder.clearCallingIdentity(); try { - UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, singleShot, - receiver, callingUid); - UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, r); - if (oldRecord != null) { - oldRecord.disposeLocked(); - } - - if (newUid) { - p.addListener(callingUid); - } - - boolean isProviderEnabled = isAllowedBySettingsLocked(provider); - if (isProviderEnabled) { - long minTimeForProvider = getMinTimeLocked(provider); - Slog.i(TAG, "request " + provider + " (pid " + callingPid + ") " + minTime + - " " + minTimeForProvider + (singleShot ? " (singleshot)" : "")); - p.setMinTime(minTimeForProvider, mTmpWorkSource); - // try requesting single shot if singleShot is true, and fall back to - // regular location tracking if requestSingleShotFix() is not supported - if (!singleShot || !p.requestSingleShotFix()) { - p.enableLocationTracking(true); - } - } else { - // Notify the listener that updates are currently disabled - receiver.callProviderEnabledLocked(provider, false); - } - if (LOCAL_LOGV) { - Slog.v(TAG, "_requestLocationUpdates: provider = " + provider + " listener = " + receiver); + synchronized (mLock) { + requestLocationUpdatesLocked(request, recevier, pid, uid, packageName); } } finally { Binder.restoreCallingIdentity(identity); } } - @Override - public void removeUpdates(ILocationListener listener) { - try { - synchronized (mLock) { - removeUpdatesLocked(getReceiver(listener)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "removeUpdates got exception:", e); + private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver, + int pid, int uid, String packageName) { + // Figure out the provider. Either its explicitly request (legacy use cases), or + // use the fused provider + if (request == null) request = DEFAULT_LOCATION_REQUEST; + String name = request.getProvider(); + if (name == null) name = LocationManager.FUSED_PROVIDER; + LocationProviderInterface provider = mProvidersByName.get(name); + if (provider == null) { + throw new IllegalArgumentException("provider doesn't exisit: " + provider); + } + + Log.i(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver)) + " " + + name + " " + request + " from " + packageName + "(" + uid + ")"); + + UpdateRecord record = new UpdateRecord(name, request, receiver); + UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record); + if (oldRecord != null) { + oldRecord.disposeLocked(false); + } + + boolean isProviderEnabled = isAllowedBySettingsLocked(name); + if (isProviderEnabled) { + applyRequirementsLocked(name); + } else { + // Notify the listener that updates are currently disabled + receiver.callProviderEnabledLocked(name, false); } } @Override - public void removeUpdatesPI(PendingIntent intent) { + public void removeUpdates(ILocationListener listener, PendingIntent intent, + String packageName) { + checkPackageName(packageName); + checkPermission(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + Receiver receiver = checkListenerOrIntent(listener, intent, pid, uid, packageName); + + // so wakelock calls will succeed (not totally sure this is still needed) + long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - removeUpdatesLocked(getReceiver(intent)); + removeUpdatesLocked(receiver); } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "removeUpdates got exception:", e); + } finally { + Binder.restoreCallingIdentity(identity); } } private void removeUpdatesLocked(Receiver receiver) { - if (LOCAL_LOGV) { - Slog.v(TAG, "_removeUpdates: listener = " + receiver); - } + Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver))); - // so wakelock calls will succeed - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); - long identity = Binder.clearCallingIdentity(); - try { - if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) { - receiver.getListener().asBinder().unlinkToDeath(receiver, 0); - synchronized(receiver) { - if(receiver.mPendingBroadcasts > 0) { - decrementPendingBroadcasts(); - receiver.mPendingBroadcasts = 0; - } + if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) { + receiver.getListener().asBinder().unlinkToDeath(receiver, 0); + synchronized (receiver) { + if (receiver.mPendingBroadcasts > 0) { + decrementPendingBroadcasts(); + receiver.mPendingBroadcasts = 0; } } + } - // Record which providers were associated with this listener - HashSet<String> providers = new HashSet<String>(); - HashMap<String,UpdateRecord> oldRecords = receiver.mUpdateRecords; - if (oldRecords != null) { - // Call dispose() on the obsolete update records. - for (UpdateRecord record : oldRecords.values()) { - if (!providerHasListener(record.mProvider, callingUid, receiver)) { - LocationProviderInterface p = mProvidersByName.get(record.mProvider); - if (p != null) { - p.removeListener(callingUid); - } - } - record.disposeLocked(); - } - // Accumulate providers - providers.addAll(oldRecords.keySet()); + // Record which providers were associated with this listener + HashSet<String> providers = new HashSet<String>(); + HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords; + if (oldRecords != null) { + // Call dispose() on the obsolete update records. + for (UpdateRecord record : oldRecords.values()) { + record.disposeLocked(false); } + // Accumulate providers + providers.addAll(oldRecords.keySet()); + } - // See if the providers associated with this listener have any - // other listeners; if one does, inform it of the new smallest minTime - // value; if one does not, disable location tracking for it - for (String provider : providers) { - // If provider is already disabled, don't need to do anything - if (!isAllowedBySettingsLocked(provider)) { - continue; - } + // update provider + for (String provider : providers) { + // If provider is already disabled, don't need to do anything + if (!isAllowedBySettingsLocked(provider)) { + continue; + } - boolean hasOtherListener = false; - ArrayList<UpdateRecord> recordsForProvider = mRecordsByProvider.get(provider); - if (recordsForProvider != null && recordsForProvider.size() > 0) { - hasOtherListener = true; - } + applyRequirementsLocked(provider); + } + } - LocationProviderInterface p = mProvidersByName.get(provider); - if (p != null) { - if (hasOtherListener) { - long minTime = getMinTimeLocked(provider); - Slog.i(TAG, "remove " + provider + " (pid " + callingPid + - "), next minTime = " + minTime); - p.setMinTime(minTime, mTmpWorkSource); - } else { - Slog.i(TAG, "remove " + provider + " (pid " + callingPid + - "), disabled"); - p.enableLocationTracking(false); - } - } + @Override + public Location getLastLocation(LocationRequest request) { + if (D) Log.d(TAG, "getLastLocation: " + request); + if (request == null) request = DEFAULT_LOCATION_REQUEST; + String perm = checkPermissionAndRequest(request); + + synchronized (mLock) { + // Figure out the provider. Either its explicitly request (deprecated API's), + // or use the fused provider + String name = request.getProvider(); + if (name == null) name = LocationManager.FUSED_PROVIDER; + LocationProviderInterface provider = mProvidersByName.get(name); + if (provider == null) return null; + + if (!isAllowedBySettingsLocked(name)) return null; + + Location location = mLastLocation.get(name); + if (ACCESS_FINE_LOCATION.equals(perm)) { + return location; + } else { + return getCoarseLocationExtra(location); } - } finally { - Binder.restoreCallingIdentity(identity); } } @Override + public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent, + String packageName) { + if (request == null) request = DEFAULT_LOCATION_REQUEST; + checkPermissionAndRequest(request); + checkPendingIntent(intent); + checkPackageName(packageName); + + if (D) Log.d(TAG, "requestGeofence: " + request + " " + geofence + " " + intent); + + mGeofenceManager.addFence(request, geofence, intent, Binder.getCallingUid(), packageName); + } + + @Override + public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) { + checkPermission(); + checkPendingIntent(intent); + checkPackageName(packageName); + + if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent); + + mGeofenceManager.removeFence(geofence, intent); + } + + + @Override public boolean addGpsStatusListener(IGpsStatusListener listener) { if (mGpsStatusProvider == null) { return false; @@ -1405,8 +1146,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run throw new NullPointerException(); } - // first check for permission to the provider - checkPermissionsSafe(provider, null); + checkPermission(); // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) != PackageManager.PERMISSION_GRANTED)) { @@ -1415,176 +1155,113 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (mLock) { LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return false; - } + if (p == null) return false; return p.sendExtraCommand(command, extras); } } @Override - public boolean sendNiResponse(int notifId, int userResponse) - { + public boolean sendNiResponse(int notifId, int userResponse) { if (Binder.getCallingUid() != Process.myUid()) { throw new SecurityException( "calling sendNiResponse from outside of the system is not allowed"); } try { return mNetInitiatedListener.sendNiResponse(notifId, userResponse); - } - catch (RemoteException e) - { + } catch (RemoteException e) { Slog.e(TAG, "RemoteException in LocationManagerService.sendNiResponse"); return false; } } - @Override - public void addProximityAlert(double latitude, double longitude, - float radius, long expiration, PendingIntent intent, String packageName) { - validatePendingIntent(intent); - validatePackageName(Binder.getCallingUid(), packageName); - - // Require ability to access all providers for now - if (!isAllowedProviderSafe(LocationManager.GPS_PROVIDER) || - !isAllowedProviderSafe(LocationManager.NETWORK_PROVIDER)) { - throw new SecurityException("Requires ACCESS_FINE_LOCATION permission"); - } - - if (LOCAL_LOGV) Slog.v(TAG, "addProximityAlert: lat=" + latitude + ", long=" + longitude + - ", radius=" + radius + ", exp=" + expiration + ", intent = " + intent); - - mGeofenceManager.addFence(latitude, longitude, radius, expiration, intent, - Binder.getCallingUid(), packageName); - } - - @Override - public void removeProximityAlert(PendingIntent intent) { - if (intent == null) throw new NullPointerException("pending intent is null"); - - if (LOCAL_LOGV) Slog.v(TAG, "removeProximityAlert: intent = " + intent); - - mGeofenceManager.removeFence(intent); - } - /** * @return null if the provider does not exist * @throws SecurityException if the provider is not allowed to be * accessed by the caller */ @Override - public Bundle getProviderInfo(String provider) { - try { - synchronized (mLock) { - return _getProviderInfoLocked(provider); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "_getProviderInfo got exception:", e); - return null; - } - } + public ProviderProperties getProviderProperties(String provider) { + checkPermission(); - private Bundle _getProviderInfoLocked(String provider) { - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return null; + LocationProviderInterface p; + synchronized (mLock) { + p = mProvidersByName.get(provider); } - checkPermissionsSafe(provider, null); - - Bundle b = new Bundle(); - b.putBoolean("network", p.requiresNetwork()); - b.putBoolean("satellite", p.requiresSatellite()); - b.putBoolean("cell", p.requiresCell()); - b.putBoolean("cost", p.hasMonetaryCost()); - b.putBoolean("altitude", p.supportsAltitude()); - b.putBoolean("speed", p.supportsSpeed()); - b.putBoolean("bearing", p.supportsBearing()); - b.putInt("power", p.getPowerRequirement()); - b.putInt("accuracy", p.getAccuracy()); - - return b; + if (p == null) return null; + return p.getProperties(); } @Override public boolean isProviderEnabled(String provider) { - try { - synchronized (mLock) { - return _isProviderEnabledLocked(provider); - } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "isProviderEnabled got exception:", e); - return false; + checkPermission(); + if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; + + synchronized (mLock) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) return false; + + return isAllowedBySettingsLocked(provider); } } - @Override - public void reportLocation(Location location, boolean passive) { + private void checkCallerIsProvider() { if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires INSTALL_LOCATION_PROVIDER permission"); - } - - if (!location.isComplete()) { - Log.w(TAG, "Dropping incomplete location: " + location); + == PackageManager.PERMISSION_GRANTED) { return; } - mLocationHandler.removeMessages(MESSAGE_LOCATION_CHANGED, location); - Message m = Message.obtain(mLocationHandler, MESSAGE_LOCATION_CHANGED, location); - m.arg1 = (passive ? 1 : 0); - mLocationHandler.sendMessageAtFrontOfQueue(m); - } + // Previously we only used the INSTALL_LOCATION_PROVIDER + // check. But that is system or signature + // protection level which is not flexible enough for + // providers installed oustide the system image. So + // also allow providers with a UID matching the + // currently bound package name - private boolean _isProviderEnabledLocked(String provider) { - checkPermissionsSafe(provider, null); + int uid = Binder.getCallingUid(); - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return false; + if (mGeocodeProvider != null) { + if (doesPackageHaveUid(uid, mGeocodeProvider.getConnectedPackageName())) return; + } + for (LocationProviderProxy proxy : mProxyProviders) { + if (doesPackageHaveUid(uid, proxy.getConnectedPackageName())) return; } - return isAllowedBySettingsLocked(provider); + throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " + + "or UID of a currently bound location provider"); } - @Override - public Location getLastKnownLocation(String provider) { - if (LOCAL_LOGV) { - Slog.v(TAG, "getLastKnownLocation: " + provider); + private boolean doesPackageHaveUid(int uid, String packageName) { + if (packageName == null) { + return false; } try { - synchronized (mLock) { - return _getLastKnownLocationLocked(provider); + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0); + if (appInfo.uid != uid) { + return false; } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getLastKnownLocation got exception:", e); - return null; + } catch (NameNotFoundException e) { + return false; } + return true; } - private Location _getLastKnownLocationLocked(String provider) { - checkPermissionsSafe(provider, null); - - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return null; - } + @Override + public void reportLocation(Location location, boolean passive) { + checkCallerIsProvider(); - if (!isAllowedBySettingsLocked(provider)) { - return null; + if (!location.isComplete()) { + Log.w(TAG, "Dropping incomplete location: " + location); + return; } - return mLastKnownLocation.get(provider); + mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location); + Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location); + m.arg1 = (passive ? 1 : 0); + mLocationHandler.sendMessageAtFrontOfQueue(m); } + private static boolean shouldBroadcastSafe(Location loc, Location lastLoc, UpdateRecord record) { // Always broadcast the first update if (lastLoc == null) { @@ -1592,14 +1269,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } // Check whether sufficient time has passed - long minTime = record.mMinTime; + long minTime = record.mRequest.getFastestInterval(); long delta = (loc.getElapsedRealtimeNano() - lastLoc.getElapsedRealtimeNano()) / 1000000L; - if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER) { + if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) { return false; } // Check whether sufficient distance has been traveled - double minDistance = record.mMinDistance; + double minDistance = record.mRequest.getSmallestDisplacement(); if (minDistance > 0.0) { if (loc.distanceTo(lastLoc) <= minDistance) { return false; @@ -1610,24 +1287,27 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } private void handleLocationChangedLocked(Location location, boolean passive) { + long now = SystemClock.elapsedRealtime(); String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider()); ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - if (records == null || records.size() == 0) { - return; - } + if (records == null || records.size() == 0) return; LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return; + if (p == null) return; + + // Add the coarse location as an extra, if not already present + Location coarse = getCoarseLocationExtra(location); + if (coarse == null) { + coarse = addCoarseLocationExtra(location); } - // Update last known location for provider - Location lastLocation = mLastKnownLocation.get(provider); + // Update last known locations + Location lastLocation = mLastLocation.get(provider); if (lastLocation == null) { - mLastKnownLocation.put(provider, new Location(location)); - } else { - lastLocation.set(location); + lastLocation = new Location(provider); + mLastLocation.put(provider, lastLocation); } + lastLocation.set(location); // Fetch latest status update time long newStatusUpdateTime = p.getStatusUpdateTime(); @@ -1637,13 +1317,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run int status = p.getStatus(extras); ArrayList<Receiver> deadReceivers = null; + ArrayList<UpdateRecord> deadUpdateRecords = null; // Broadcast location or status to all listeners - final int N = records.size(); - for (int i=0; i<N; i++) { - UpdateRecord r = records.get(i); + for (UpdateRecord r : records) { Receiver receiver = r.mReceiver; boolean receiverDead = false; + if (ACCESS_FINE_LOCATION.equals(receiver.mPermission)) { + location = lastLocation; // use fine location + } else { + location = coarse; // use coarse location + } Location lastLoc = r.mLastFixBroadcast; if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) { @@ -1670,8 +1354,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - // remove receiver if it is dead or we just processed a single shot request - if (receiverDead || r.mSingleShot) { + // track expired records + if (r.mRequest.getNumUpdates() == 0 || r.mRequest.getExpireAt() < now) { + if (deadUpdateRecords == null) { + deadUpdateRecords = new ArrayList<UpdateRecord>(); + } + deadUpdateRecords.add(r); + } + // track dead receivers + if (receiverDead) { if (deadReceivers == null) { deadReceivers = new ArrayList<Receiver>(); } @@ -1681,162 +1372,71 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + // remove dead records and receivers outside the loop if (deadReceivers != null) { - for (int i=deadReceivers.size()-1; i>=0; i--) { - removeUpdatesLocked(deadReceivers.get(i)); + for (Receiver receiver : deadReceivers) { + removeUpdatesLocked(receiver); + } + } + if (deadUpdateRecords != null) { + for (UpdateRecord r : deadUpdateRecords) { + r.disposeLocked(true); } } } private class LocationWorkerHandler extends Handler { - @Override public void handleMessage(Message msg) { - try { - if (msg.what == MESSAGE_LOCATION_CHANGED) { - // log("LocationWorkerHandler: MESSAGE_LOCATION_CHANGED!"); - - synchronized (mLock) { - Location location = (Location) msg.obj; - String provider = location.getProvider(); - boolean passive = (msg.arg1 == 1); - - if (!passive) { - // notify other providers of the new location - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - if (!provider.equals(p.getName())) { - p.updateLocation(location); - } - } - } + switch (msg.what) { + case MSG_LOCATION_CHANGED: + handleLocationChanged((Location) msg.obj, msg.arg1 == 1); + break; + } + } + } - if (isAllowedBySettingsLocked(provider)) { - handleLocationChangedLocked(location, passive); - } - } - } else if (msg.what == MESSAGE_PACKAGE_UPDATED) { - String packageName = (String) msg.obj; - - // reconnect to external providers if there is a better package - if (mNetworkLocationProviderPackageName != null && - mPackageManager.resolveService( - new Intent(LocationProviderProxy.SERVICE_ACTION) - .setPackage(packageName), 0) != null) { - // package implements service, perform full check - String bestPackage = findBestPackage( - LocationProviderProxy.SERVICE_ACTION, - mNetworkLocationProviderPackageName); - if (packageName.equals(bestPackage)) { - mNetworkLocationProvider.reconnect(bestPackage); - mNetworkLocationProviderPackageName = packageName; - } - } - if (mGeocodeProviderPackageName != null && - mPackageManager.resolveService( - new Intent(GeocoderProxy.SERVICE_ACTION) - .setPackage(packageName), 0) != null) { - // package implements service, perform full check - String bestPackage = findBestPackage( - GeocoderProxy.SERVICE_ACTION, - mGeocodeProviderPackageName); - if (packageName.equals(bestPackage)) { - mGeocodeProvider.reconnect(bestPackage); - mGeocodeProviderPackageName = packageName; - } - } - } - } catch (Exception e) { - // Log, don't crash! - Slog.e(TAG, "Exception in LocationWorkerHandler.handleMessage:", e); + private void handleLocationChanged(Location location, boolean passive) { + String provider = location.getProvider(); + + if (!passive) { + // notify passive provider of the new location + mPassiveProvider.updateLocation(location); + } + + synchronized (mLock) { + if (isAllowedBySettingsLocked(provider)) { + handleLocationChangedLocked(location, passive); } } } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - boolean queryRestart = action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART); - if (queryRestart - || action.equals(Intent.ACTION_PACKAGE_REMOVED) - || action.equals(Intent.ACTION_PACKAGE_RESTARTED) - || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - synchronized (mLock) { - int uidList[] = null; - if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); - } else { - uidList = new int[]{intent.getIntExtra(Intent.EXTRA_UID, -1)}; - } - if (uidList == null || uidList.length == 0) { - return; - } - for (int uid : uidList) { - if (uid >= 0) { - ArrayList<Receiver> removedRecs = null; - for (ArrayList<UpdateRecord> i : mRecordsByProvider.values()) { - for (int j=i.size()-1; j>=0; j--) { - UpdateRecord ur = i.get(j); - if (ur.mReceiver.isPendingIntent() && ur.mUid == uid) { - if (queryRestart) { - setResultCode(Activity.RESULT_OK); - return; - } - if (removedRecs == null) { - removedRecs = new ArrayList<Receiver>(); - } - if (!removedRecs.contains(ur.mReceiver)) { - removedRecs.add(ur.mReceiver); - } - } - } - } + public void onPackageDisappeared(String packageName, int reason) { + // remove all receivers associated with this package name + synchronized (mLock) { + ArrayList<Receiver> deadReceivers = null; + + for (Receiver receiver : mReceivers.values()) { + if (receiver.mPackageName.equals(packageName)) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<Receiver>(); } + deadReceivers.add(receiver); } } - } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - boolean noConnectivity = - intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - if (!noConnectivity) { - mNetworkState = LocationProvider.AVAILABLE; - } else { - mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; - } - - final NetworkInfo info = intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); - // Notify location providers of current network state - synchronized (mLock) { - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface provider = mProviders.get(i); - if (provider.requiresNetwork()) { - provider.updateNetworkState(mNetworkState, info); - } + // perform removal outside of mReceivers loop + if (deadReceivers != null) { + for (Receiver receiver : deadReceivers) { + removeUpdatesLocked(receiver); } } } } }; - private final PackageMonitor mPackageMonitor = new PackageMonitor() { - @Override - public void onPackageUpdateFinished(String packageName, int uid) { - // Called by main thread; divert work to LocationWorker. - Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget(); - } - @Override - public void onPackageAdded(String packageName, int uid) { - // Called by main thread; divert work to LocationWorker. - Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget(); - } - @Override - public void onPackageDisappeared(String packageName, int uid) { - mGeofenceManager.removeFence(packageName); - } - }; - // Wake locks private void incrementPendingBroadcasts() { @@ -1922,9 +1522,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } @Override - public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite, - boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, - boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + public void addTestProvider(String name, ProviderProperties properties) { checkMockPermissionsSafe(); if (LocationManager.PASSIVE_PROVIDER.equals(name)) { @@ -1933,25 +1531,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run long identity = Binder.clearCallingIdentity(); synchronized (mLock) { - MockProvider provider = new MockProvider(name, this, - requiresNetwork, requiresSatellite, - requiresCell, hasMonetaryCost, supportsAltitude, - supportsSpeed, supportsBearing, powerRequirement, accuracy); + MockProvider provider = new MockProvider(name, this, properties); // remove the real provider if we are replacing GPS or network provider if (LocationManager.GPS_PROVIDER.equals(name) || LocationManager.NETWORK_PROVIDER.equals(name)) { LocationProviderInterface p = mProvidersByName.get(name); if (p != null) { - p.enableLocationTracking(false); - removeProvider(p); + removeProviderLocked(p); } } if (mProvidersByName.get(name) != null) { throw new IllegalArgumentException("Provider \"" + name + "\" already exists"); } - addProvider(provider); + addProviderLocked(provider); mMockProviders.put(name, provider); - mLastKnownLocation.put(name, null); + mLastLocation.put(name, null); updateProvidersLocked(); } Binder.restoreCallingIdentity(identity); @@ -1966,17 +1560,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } long identity = Binder.clearCallingIdentity(); - removeProvider(mProvidersByName.get(provider)); + removeProviderLocked(mProvidersByName.get(provider)); mMockProviders.remove(mockProvider); - // reinstall real provider if we were mocking GPS or network provider - if (LocationManager.GPS_PROVIDER.equals(provider) && - mGpsLocationProvider != null) { - addProvider(mGpsLocationProvider); - } else if (LocationManager.NETWORK_PROVIDER.equals(provider) && - mNetworkLocationProvider != null) { - addProvider(mNetworkLocationProvider); - } - mLastKnownLocation.put(provider, null); + + // reinstate real provider if available + LocationProviderInterface realProvider = mRealProviders.get(provider); + if (realProvider != null) { + addProviderLocked(realProvider); + } + mLastLocation.put(provider, null); updateProvidersLocked(); Binder.restoreCallingIdentity(identity); } @@ -2072,6 +1664,106 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + private static double wrapLatitude(double lat) { + if (lat > MAX_LATITUDE) lat = MAX_LATITUDE; + if (lat < -MAX_LATITUDE) lat = -MAX_LATITUDE; + return lat; + } + + private static double wrapLongitude(double lon) { + if (lon >= 180.0) lon -= 360.0; + if (lon < -180.0) lon += 360.0; + return lon; + } + + private static double distanceToDegreesLatitude(double distance) { + return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR; + } + + /** + * Requires latitude since longitudinal distances change with distance from equator. + */ + private static double distanceToDegreesLongitude(double distance, double lat) { + return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / Math.cos(lat); + } + + /** + * Fudge a location into a coarse location. + * <p>Add a random offset, then quantize the result (snap-to-grid). + * Random offsets alone can be low-passed pretty easily. + * Snap-to-grid on its own is excellent unless you are sitting on a + * grid boundary and bouncing between quantizations. + * The combination is quite hard to reverse engineer. + * <p>The random offset used is smaller than the goal accuracy + * ({@link #COARSE_ACCURACY_M}), in order to give relatively stable + * results after quantization. + */ + private static Location createCoarse(Location fine) { + Location coarse = new Location(fine); + + coarse.removeBearing(); + coarse.removeSpeed(); + coarse.removeAltitude(); + + double lat = coarse.getLatitude(); + double lon = coarse.getLongitude(); + + // wrap + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + if (coarse.getAccuracy() < COARSE_ACCURACY_M / 2) { + // apply a random offset + double fudgeDistance = COARSE_ACCURACY_M / 2.0 - coarse.getAccuracy(); + lat += (Math.random() - 0.5) * distanceToDegreesLatitude(fudgeDistance); + lon += (Math.random() - 0.5) * distanceToDegreesLongitude(fudgeDistance, lat); + } + + // wrap + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + // quantize (snap-to-grid) + double latGranularity = distanceToDegreesLatitude(COARSE_ACCURACY_M); + double lonGranularity = distanceToDegreesLongitude(COARSE_ACCURACY_M, lat); + long latQuantized = Math.round(lat / latGranularity); + long lonQuantized = Math.round(lon / lonGranularity); + lat = latQuantized * latGranularity; + lon = lonQuantized * lonGranularity; + + // wrap again + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + // apply + coarse.setLatitude(lat); + coarse.setLongitude(lon); + coarse.setAccuracy((float)COARSE_ACCURACY_M); + + return coarse; + } + + + private static Location getCoarseLocationExtra(Location location) { + Bundle extras = location.getExtras(); + if (extras == null) return null; + Parcelable parcel = extras.getParcelable(EXTRA_COARSE_LOCATION); + if (parcel == null) return null; + if (!(parcel instanceof Location)) return null; + Location coarse = (Location) parcel; + if (coarse.getAccuracy() < COARSE_ACCURACY_M) return null; + return coarse; + } + + private static Location addCoarseLocationExtra(Location location) { + Bundle extras = location.getExtras(); + if (extras == null) extras = new Bundle(); + Location coarse = createCoarse(location); + extras.putParcelable(EXTRA_COARSE_LOCATION, coarse); + location.setExtras(extras); + return coarse; + } + private void log(String log) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Slog.d(TAG, log); @@ -2090,36 +1782,26 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (mLock) { pw.println("Current Location Manager state:"); - pw.println(" sProvidersLoaded=" + sProvidersLoaded); - pw.println(" Listeners:"); - int N = mReceivers.size(); - for (int i=0; i<N; i++) { - pw.println(" " + mReceivers.get(i)); - } pw.println(" Location Listeners:"); - for (Receiver i : mReceivers.values()) { - pw.println(" " + i + ":"); - for (Map.Entry<String,UpdateRecord> j : i.mUpdateRecords.entrySet()) { - pw.println(" " + j.getKey() + ":"); - j.getValue().dump(pw, " "); - } + for (Receiver receiver : mReceivers.values()) { + pw.println(" " + receiver); } pw.println(" Records by Provider:"); - for (Map.Entry<String, ArrayList<UpdateRecord>> i - : mRecordsByProvider.entrySet()) { - pw.println(" " + i.getKey() + ":"); - for (UpdateRecord j : i.getValue()) { - pw.println(" " + j + ":"); - j.dump(pw, " "); + for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) { + pw.println(" " + entry.getKey() + ":"); + for (UpdateRecord record : entry.getValue()) { + pw.println(" " + record); } } pw.println(" Last Known Locations:"); - for (Map.Entry<String, Location> i - : mLastKnownLocation.entrySet()) { - pw.println(" " + i.getKey() + ":"); - i.getValue().dump(new PrintWriterPrinter(pw), " "); + for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) { + String provider = entry.getKey(); + Location location = entry.getValue(); + pw.println(" " + provider + ": " + location); } + mGeofenceManager.dump(pw); + if (mEnabledProviders.size() > 0) { pw.println(" Enabled Providers:"); for (String i : mEnabledProviders) { @@ -2140,12 +1822,18 @@ public class LocationManagerService extends ILocationManager.Stub implements Run i.getValue().dump(pw, " "); } } + + if (args.length > 0 && "short".equals(args[0])) { + return; + } for (LocationProviderInterface provider: mProviders) { - String state = provider.getInternalState(); - if (state != null) { - pw.println(provider.getName() + " Internal State:"); - pw.write(state); + pw.print(provider.getName() + " Internal State"); + if (provider instanceof LocationProviderProxy) { + LocationProviderProxy proxy = (LocationProviderProxy) provider; + pw.print(" (" + proxy.getConnectedPackageName() + ")"); } + pw.println(":"); + provider.dump(fd, pw, args); } } } diff --git a/services/java/com/android/server/ServiceWatcher.java b/services/java/com/android/server/ServiceWatcher.java new file mode 100644 index 0000000..0dfaa05 --- /dev/null +++ b/services/java/com/android/server/ServiceWatcher.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.Signature; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +import com.android.internal.content.PackageMonitor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +/** + * Find the best Service, and bind to it. + * Handles run-time package changes. + */ +public class ServiceWatcher implements ServiceConnection { + private static final boolean D = false; + private static final String EXTRA_VERSION = "version"; + + private final String mTag; + private final Context mContext; + private final PackageManager mPm; + private final List<HashSet<Signature>> mSignatureSets; + private final String mAction; + private final Runnable mNewServiceWork; + private final Handler mHandler; + + private Object mLock = new Object(); + + // all fields below synchronized on mLock + private IBinder mBinder; // connected service + private String mPackageName; // current best package + private int mVersion; // current best version + + public ServiceWatcher(Context context, String logTag, String action, + List<String> initialPackageNames, Runnable newServiceWork, Handler handler) { + mContext = context; + mTag = logTag; + mAction = action; + mPm = mContext.getPackageManager(); + mNewServiceWork = newServiceWork; + mHandler = handler; + + mSignatureSets = new ArrayList<HashSet<Signature>>(); + for (int i=0; i < initialPackageNames.size(); i++) { + String pkg = initialPackageNames.get(i); + HashSet<Signature> set = new HashSet<Signature>(); + try { + Signature[] sigs = + mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures; + set.addAll(Arrays.asList(sigs)); + mSignatureSets.add(set); + } catch (NameNotFoundException e) { + Log.w(logTag, pkg + " not found"); + } + } + + } + + public boolean start() { + if (!bindBestPackage(null)) return false; + + mPackageMonitor.register(mContext, null, true); + return true; + } + + /** + * Searches and binds to the best package, or do nothing + * if the best package is already bound. + * Only checks the named package, or checks all packages if it + * is null. + * Return true if a new package was found to bind to. + */ + private boolean bindBestPackage(String justCheckThisPackage) { + Intent intent = new Intent(mAction); + if (justCheckThisPackage != null) { + intent.setPackage(justCheckThisPackage); + } + List<ResolveInfo> rInfos = mPm.queryIntentServices(new Intent(mAction), + PackageManager.GET_META_DATA); + int bestVersion = Integer.MIN_VALUE; + String bestPackage = null; + for (ResolveInfo rInfo : rInfos) { + String packageName = rInfo.serviceInfo.packageName; + + // check signature + try { + PackageInfo pInfo; + pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + if (!isSignatureMatch(pInfo.signatures)) { + Log.w(mTag, packageName + " resolves service " + mAction + + ", but has wrong signature, ignoring"); + continue; + } + } catch (NameNotFoundException e) { + Log.wtf(mTag, e); + continue; + } + + // check version + int version = 0; + if (rInfo.serviceInfo.metaData != null) { + version = rInfo.serviceInfo.metaData.getInt(EXTRA_VERSION, 0); + } + if (version > mVersion) { + bestVersion = version; + bestPackage = packageName; + } + } + + if (D) Log.d(mTag, String.format("bindBestPackage %s found %d, %s", + (justCheckThisPackage == null ? "" : "(" + justCheckThisPackage + ") "), + rInfos.size(), + (bestPackage == null ? "no new best package" : "new best packge: " + bestPackage))); + + if (bestPackage != null) { + bindToPackage(bestPackage, bestVersion); + return true; + } + return false; + } + + private void unbind() { + String pkg; + synchronized (mLock) { + pkg = mPackageName; + mPackageName = null; + mVersion = Integer.MIN_VALUE; + } + if (pkg != null) { + if (D) Log.d(mTag, "unbinding " + pkg); + mContext.unbindService(this); + } + } + + private void bindToPackage(String packageName, int version) { + unbind(); + Intent intent = new Intent(mAction); + intent.setPackage(packageName); + synchronized (mLock) { + mPackageName = packageName; + mVersion = version; + } + if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ")"); + mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT); + } + + private boolean isSignatureMatch(Signature[] signatures) { + if (signatures == null) return false; + + // build hashset of input to test against + HashSet<Signature> inputSet = new HashSet<Signature>(); + for (Signature s : signatures) { + inputSet.add(s); + } + + // test input against each of the signature sets + for (HashSet<Signature> referenceSet : mSignatureSets) { + if (referenceSet.equals(inputSet)) { + return true; + } + } + return false; + } + + private final PackageMonitor mPackageMonitor = new PackageMonitor() { + /** + * Called when package has been reinstalled + */ + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + // package updated, make sure to rebind + unbind(); + } + // check the updated package in case it is better + bindBestPackage(packageName); + } + + @Override + public void onPackageAdded(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + // package updated, make sure to rebind + unbind(); + } + // check the new package is case it is better + bindBestPackage(packageName); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + unbind(); + // the currently bound package was removed, + // need to search for a new package + bindBestPackage(null); + } + } + }; + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + synchronized (mLock) { + String packageName = name.getPackageName(); + if (packageName.equals(mPackageName)) { + if (D) Log.d(mTag, packageName + " connected"); + mBinder = binder; + if (mHandler !=null && mNewServiceWork != null) { + mHandler.post(mNewServiceWork); + } + } else { + Log.w(mTag, "unexpected onServiceConnected: " + packageName); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + String packageName = name.getPackageName(); + if (D) Log.d(mTag, packageName + " disconnected"); + + if (packageName.equals(mPackageName)) { + mBinder = null; + } + } + } + + public String getBestPackageName() { + synchronized (mLock) { + return mPackageName; + } + } + + public int getBestVersion() { + synchronized (mLock) { + return mVersion; + } + } + + public IBinder getBinder() { + synchronized (mLock) { + return mBinder; + } + } +} diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java index 07f3125..7d030e9 100644 --- a/services/java/com/android/server/location/GeocoderProxy.java +++ b/services/java/com/android/server/location/GeocoderProxy.java @@ -16,92 +16,64 @@ package com.android.server.location; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; import android.location.Address; import android.location.GeocoderParams; import android.location.IGeocodeProvider; -import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; import android.util.Log; +import com.android.server.ServiceWatcher; import java.util.List; /** - * A class for proxying IGeocodeProvider implementations. - * - * {@hide} + * Proxy for IGeocodeProvider implementations. */ public class GeocoderProxy { - private static final String TAG = "GeocoderProxy"; - public static final String SERVICE_ACTION = - "com.android.location.service.GeocodeProvider"; + private static final String SERVICE_ACTION = "com.android.location.service.GeocodeProvider"; private final Context mContext; - private final Intent mIntent; - private final Object mMutex = new Object(); // synchronizes access to mServiceConnection - private Connection mServiceConnection; // never null after ctor - - public GeocoderProxy(Context context, String packageName) { - mContext = context; - mIntent = new Intent(SERVICE_ACTION); - reconnect(packageName); - } - - /** Bind to service. Will reconnect if already connected */ - public void reconnect(String packageName) { - synchronized (mMutex) { - if (mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - } - mServiceConnection = new Connection(); - mIntent.setPackage(packageName); - mContext.bindService(mIntent, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND - | Context.BIND_ALLOW_OOM_MANAGEMENT); + private final ServiceWatcher mServiceWatcher; + + public static GeocoderProxy createAndBind(Context context, + List<String> initialPackageNames) { + GeocoderProxy proxy = new GeocoderProxy(context, initialPackageNames); + if (proxy.bind()) { + return proxy; + } else { + return null; } } - private class Connection implements ServiceConnection { + public GeocoderProxy(Context context, List<String> initialPackageNames) { + mContext = context; - private IGeocodeProvider mProvider; + mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, initialPackageNames, + null, null); + } - public void onServiceConnected(ComponentName className, IBinder service) { - synchronized (this) { - mProvider = IGeocodeProvider.Stub.asInterface(service); - } - } + private boolean bind () { + return mServiceWatcher.start(); + } - public void onServiceDisconnected(ComponentName className) { - synchronized (this) { - mProvider = null; - } - } + private IGeocodeProvider getService() { + return IGeocodeProvider.Stub.asInterface(mServiceWatcher.getBinder()); + } - public IGeocodeProvider getProvider() { - synchronized (this) { - return mProvider; - } - } + public String getConnectedPackageName() { + return mServiceWatcher.getBestPackageName(); } public String getFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) { - IGeocodeProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } + IGeocodeProvider provider = getService(); if (provider != null) { try { - return provider.getFromLocation(latitude, longitude, maxResults, - params, addrs); + return provider.getFromLocation(latitude, longitude, maxResults, params, addrs); } catch (RemoteException e) { - Log.e(TAG, "getFromLocation failed", e); + Log.w(TAG, e); } } return "Service not Available"; @@ -111,19 +83,17 @@ public class GeocoderProxy { double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, int maxResults, GeocoderParams params, List<Address> addrs) { - IGeocodeProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } + IGeocodeProvider provider = getService(); if (provider != null) { try { return provider.getFromLocationName(locationName, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, maxResults, params, addrs); } catch (RemoteException e) { - Log.e(TAG, "getFromLocationName failed", e); + Log.w(TAG, e); } } return "Service not Available"; } + } diff --git a/services/java/com/android/server/location/GeofenceManager.java b/services/java/com/android/server/location/GeofenceManager.java index b3f53ea..338cd5d 100644 --- a/services/java/com/android/server/location/GeofenceManager.java +++ b/services/java/com/android/server/location/GeofenceManager.java @@ -25,47 +25,36 @@ import android.Manifest.permission; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.location.Geofence; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; +import android.location.LocationRequest; import android.os.Bundle; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; public class GeofenceManager implements LocationListener, PendingIntent.OnFinished { - static final String TAG = "GeofenceManager"; + private static final String TAG = "GeofenceManager"; /** * Assume a maximum land speed, as a heuristic to throttle location updates. * (Air travel should result in an airplane mode toggle which will * force a new location update anyway). */ - static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) - - class GeofenceWrapper { - final Geofence fence; - final long expiry; - final String packageName; - final PendingIntent intent; - - public GeofenceWrapper(Geofence fence, long expiry, String packageName, - PendingIntent intent) { - this.fence = fence; - this.expiry = expiry; - this.packageName = packageName; - this.intent = intent; - } - } + private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) - final Context mContext; - final LocationManager mLocationManager; - final PowerManager.WakeLock mWakeLock; - final Looper mLooper; // looper thread to take location updates on + private final Context mContext; + private final LocationManager mLocationManager; + private final PowerManager.WakeLock mWakeLock; + private final Looper mLooper; // looper thread to take location updates on - // access to members below is synchronized on this - Location mLastLocation; - List<GeofenceWrapper> mFences = new LinkedList<GeofenceWrapper>(); + private Object mLock = new Object(); + + // access to members below is synchronized on mLock + private Location mLastLocation; + private List<GeofenceState> mFences = new LinkedList<GeofenceState>(); public GeofenceManager(Context context) { mContext = context; @@ -73,82 +62,98 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mLooper = Looper.myLooper(); - mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this); - } - public void addFence(double latitude, double longitude, float radius, long expiration, - PendingIntent intent, int uid, String packageName) { - long expiry = SystemClock.elapsedRealtime() + expiration; - if (expiration < 0) { - expiry = Long.MAX_VALUE; - } - Geofence fence = new Geofence(latitude, longitude, radius, mLastLocation); - GeofenceWrapper fenceWrapper = new GeofenceWrapper(fence, expiry, packageName, intent); - - synchronized (this) { - mFences.add(fenceWrapper); - updateProviderRequirements(); + LocationRequest request = new LocationRequest() + .setQuality(LocationRequest.POWER_NONE) + .setFastestInterval(0); + mLocationManager.requestLocationUpdates(request, this, Looper.myLooper()); + } + + public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, int uid, + String packageName) { + GeofenceState state = new GeofenceState(geofence, mLastLocation, + request.getExpireAt(), packageName, intent); + + synchronized (mLock) { + // first make sure it doesn't already exist + for (int i = mFences.size() - 1; i >= 0; i--) { + GeofenceState w = mFences.get(i); + if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) { + // already exists, remove the old one + mFences.remove(i); + break; + } + } + mFences.add(state); + updateProviderRequirementsLocked(); } } - public void removeFence(PendingIntent intent) { - synchronized (this) { - Iterator<GeofenceWrapper> iter = mFences.iterator(); + public void removeFence(Geofence fence, PendingIntent intent) { + synchronized (mLock) { + Iterator<GeofenceState> iter = mFences.iterator(); while (iter.hasNext()) { - GeofenceWrapper fenceWrapper = iter.next(); - if (fenceWrapper.intent.equals(intent)) { - iter.remove(); + GeofenceState state = iter.next(); + if (state.mIntent.equals(intent)) { + + if (fence == null) { + // alwaus remove + iter.remove(); + } else { + // just remove matching fences + if (fence.equals(state.mFence)) { + iter.remove(); + } + } } } - updateProviderRequirements(); + updateProviderRequirementsLocked(); } } public void removeFence(String packageName) { - synchronized (this) { - Iterator<GeofenceWrapper> iter = mFences.iterator(); + synchronized (mLock) { + Iterator<GeofenceState> iter = mFences.iterator(); while (iter.hasNext()) { - GeofenceWrapper fenceWrapper = iter.next(); - if (fenceWrapper.packageName.equals(packageName)) { + GeofenceState state = iter.next(); + if (state.mPackageName.equals(packageName)) { iter.remove(); } } - updateProviderRequirements(); + updateProviderRequirementsLocked(); } } - void removeExpiredFences() { - synchronized (this) { - long time = SystemClock.elapsedRealtime(); - Iterator<GeofenceWrapper> iter = mFences.iterator(); - while (iter.hasNext()) { - GeofenceWrapper fenceWrapper = iter.next(); - if (fenceWrapper.expiry < time) { - iter.remove(); - } + private void removeExpiredFencesLocked() { + long time = SystemClock.elapsedRealtime(); + Iterator<GeofenceState> iter = mFences.iterator(); + while (iter.hasNext()) { + GeofenceState state = iter.next(); + if (state.mExpireAt < time) { + iter.remove(); } } } - void processLocation(Location location) { + private void processLocation(Location location) { List<PendingIntent> enterIntents = new LinkedList<PendingIntent>(); List<PendingIntent> exitIntents = new LinkedList<PendingIntent>(); - synchronized (this) { + synchronized (mLock) { mLastLocation = location; - removeExpiredFences(); + removeExpiredFencesLocked(); - for (GeofenceWrapper fenceWrapper : mFences) { - int event = fenceWrapper.fence.processLocation(location); - if ((event & Geofence.FLAG_ENTER) != 0) { - enterIntents.add(fenceWrapper.intent); + for (GeofenceState state : mFences) { + int event = state.processLocation(location); + if ((event & GeofenceState.FLAG_ENTER) != 0) { + enterIntents.add(state.mIntent); } - if ((event & Geofence.FLAG_EXIT) != 0) { - exitIntents.add(fenceWrapper.intent); + if ((event & GeofenceState.FLAG_EXIT) != 0) { + exitIntents.add(state.mIntent); } } - updateProviderRequirements(); + updateProviderRequirementsLocked(); } // release lock before sending intents @@ -160,52 +165,50 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish } } - void sendIntentEnter(PendingIntent pendingIntent) { + private void sendIntentEnter(PendingIntent pendingIntent) { Intent intent = new Intent(); intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); sendIntent(pendingIntent, intent); } - void sendIntentExit(PendingIntent pendingIntent) { + private void sendIntentExit(PendingIntent pendingIntent) { Intent intent = new Intent(); intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); sendIntent(pendingIntent, intent); } - void sendIntent(PendingIntent pendingIntent, Intent intent) { + private void sendIntent(PendingIntent pendingIntent, Intent intent) { try { mWakeLock.acquire(); pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION); } catch (PendingIntent.CanceledException e) { - removeFence(pendingIntent); + removeFence(null, pendingIntent); mWakeLock.release(); } } - void updateProviderRequirements() { - synchronized (this) { - double minDistance = Double.MAX_VALUE; - for (GeofenceWrapper alert : mFences) { - if (alert.fence.getDistance() < minDistance) { - minDistance = alert.fence.getDistance(); - } + private void updateProviderRequirementsLocked() { + double minDistance = Double.MAX_VALUE; + for (GeofenceState state : mFences) { + if (state.getDistance() < minDistance) { + minDistance = state.getDistance(); } + } - if (minDistance == Double.MAX_VALUE) { - disableLocation(); - } else { - int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; - setLocationInterval(intervalMs); - } + if (minDistance == Double.MAX_VALUE) { + disableLocationLocked(); + } else { + int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; + requestLocationLocked(intervalMs); } } - void setLocationInterval(int intervalMs) { - mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, intervalMs, 0, this, + private void requestLocationLocked(int intervalMs) { + mLocationManager.requestLocationUpdates(new LocationRequest().setInterval(intervalMs), this, mLooper); } - void disableLocation() { + private void disableLocationLocked() { mLocationManager.removeUpdates(this); } @@ -231,11 +234,12 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish public void dump(PrintWriter pw) { pw.println(" Geofences:"); - for (GeofenceWrapper fenceWrapper : mFences) { + + for (GeofenceState state : mFences) { pw.append(" "); - pw.append(fenceWrapper.packageName); + pw.append(state.mPackageName); pw.append(" "); - pw.append(fenceWrapper.fence.toString()); + pw.append(state.mFence.toString()); pw.append("\n"); } } diff --git a/services/java/com/android/server/location/Geofence.java b/services/java/com/android/server/location/GeofenceState.java index f63607a..1fd737f 100644 --- a/services/java/com/android/server/location/Geofence.java +++ b/services/java/com/android/server/location/GeofenceState.java @@ -17,37 +17,42 @@ package com.android.server.location; +import android.app.PendingIntent; +import android.location.Geofence; import android.location.Location; /** - * Represents a simple circular GeoFence. + * Represents state associated with a geofence */ -public class Geofence { +public class GeofenceState { public final static int FLAG_ENTER = 0x01; public final static int FLAG_EXIT = 0x02; - static final int STATE_UNKNOWN = 0; - static final int STATE_INSIDE = 1; - static final int STATE_OUTSIDE = 2; + private static final int STATE_UNKNOWN = 0; + private static final int STATE_INSIDE = 1; + private static final int STATE_OUTSIDE = 2; - final double mLatitude; - final double mLongitude; - final float mRadius; - final Location mLocation; + public final Geofence mFence; + private final Location mLocation; + public final long mExpireAt; + public final String mPackageName; + public final PendingIntent mIntent; int mState; // current state double mDistance; // current distance to center of fence - public Geofence(double latitude, double longitude, float radius, Location prevLocation) { + public GeofenceState(Geofence fence, Location prevLocation, long expireAt, + String packageName, PendingIntent intent) { mState = STATE_UNKNOWN; - mLatitude = latitude; - mLongitude = longitude; - mRadius = radius; + mFence = fence; + mExpireAt = expireAt; + mPackageName = packageName; + mIntent = intent; mLocation = new Location(""); - mLocation.setLatitude(latitude); - mLocation.setLongitude(longitude); + mLocation.setLatitude(fence.getLatitude()); + mLocation.setLongitude(fence.getLongitude()); if (prevLocation != null) { processLocation(prevLocation); @@ -63,7 +68,7 @@ public class Geofence { int prevState = mState; //TODO: inside/outside detection could be made more rigorous - boolean inside = mDistance <= Math.max(mRadius, location.getAccuracy()); + boolean inside = mDistance <= Math.max(mFence.getRadius(), location.getAccuracy()); if (inside) { mState = STATE_INSIDE; } else { @@ -94,7 +99,6 @@ public class Geofence { default: state = "?"; } - return String.format("(%.4f, %.4f r=%.0f d=%.0f %s)", mLatitude, mLongitude, mRadius, - mDistance, state); + return String.format("%s d=%.0f %s", mFence.toString(), mDistance, state); } } diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index bd7668b..3cd767d 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -29,6 +29,7 @@ import android.location.IGpsStatusProvider; import android.location.ILocationManager; import android.location.INetInitiatedListener; import android.location.Location; +import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.net.ConnectivityManager; @@ -54,17 +55,19 @@ import android.telephony.TelephonyManager; import android.telephony.gsm.GsmCellLocation; import android.util.Log; import android.util.NtpTrustedTime; -import android.util.SparseIntArray; - import com.android.internal.app.IBatteryStats; import com.android.internal.location.GpsNetInitiatedHandler; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; +import java.io.PrintWriter; import java.io.StringReader; import java.util.ArrayList; import java.util.Date; @@ -84,6 +87,10 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); + private static final ProviderProperties PROPERTIES = new ProviderProperties( + true, true, false, false, true, true, true, + Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); + // these need to match GpsPositionMode enum in gps.h private static final int GPS_POSITION_MODE_STANDALONE = 0; private static final int GPS_POSITION_MODE_MS_BASED = 1; @@ -150,14 +157,13 @@ public class GpsLocationProvider implements LocationProviderInterface { // Handler messages private static final int CHECK_LOCATION = 1; private static final int ENABLE = 2; - private static final int ENABLE_TRACKING = 3; + private static final int SET_REQUEST = 3; private static final int UPDATE_NETWORK_STATE = 4; private static final int INJECT_NTP_TIME = 5; private static final int DOWNLOAD_XTRA_DATA = 6; private static final int UPDATE_LOCATION = 7; private static final int ADD_LISTENER = 8; private static final int REMOVE_LISTENER = 9; - private static final int REQUEST_SINGLE_SHOT = 10; // Request setid private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1; @@ -179,6 +185,18 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final String PROPERTIES_FILE = "/etc/gps.conf"; + /** simpler wrapper for ProviderRequest + Worksource */ + private static class GpsRequest { + public ProviderRequest request; + public WorkSource source; + public GpsRequest(ProviderRequest request, WorkSource source) { + this.request = request; + this.source = source; + } + } + + private Object mLock = new Object(); + private int mLocationFlags = LOCATION_INVALID; // current status @@ -198,9 +216,16 @@ public class GpsLocationProvider implements LocationProviderInterface { // Typical hot TTTF is ~5 seconds, so 10 seconds seems sane. private static final int GPS_POLLING_THRESHOLD_INTERVAL = 10 * 1000; - // true if we are enabled - private volatile boolean mEnabled; - + // how often to request NTP time, in milliseconds + // current setting 24 hours + private static final long NTP_INTERVAL = 24*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; + + // true if we are enabled, protected by this + private boolean mEnabled; + // true if we have network connectivity private boolean mNetworkAvailable; @@ -217,16 +242,13 @@ public class GpsLocationProvider implements LocationProviderInterface { // true if GPS engine is on private boolean mEngineOn; - + // requested frequency of fixes, in milliseconds private int mFixInterval = 1000; // true if we started navigation private boolean mStarted; - // true if single shot request is in progress - private boolean mSingleShot; - // capabilities of the GPS engine private int mEngineCapabilities; @@ -236,7 +258,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // for calculating time to first fix private long mFixRequestTime = 0; // time to first fix for most recent session - private int mTTFF = 0; + private int mTimeToFirstFix = 0; // time we received our last fix private long mLastFixTime; @@ -251,7 +273,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private final Context mContext; private final NtpTrustedTime mNtpTime; - private final ILocationManager mLocationManager; + private final ILocationManager mILocationManager; private Location mLocation = new Location(LocationManager.GPS_PROVIDER); private Bundle mLocationExtras = new Bundle(); private ArrayList<Listener> mListeners = new ArrayList<Listener>(); @@ -267,17 +289,11 @@ public class GpsLocationProvider implements LocationProviderInterface { private int mAGpsDataConnectionState; private int mAGpsDataConnectionIpAddr; private final ConnectivityManager mConnMgr; - private final GpsNetInitiatedHandler mNIHandler; + private final GpsNetInitiatedHandler mNIHandler; // Wakelocks private final static String WAKELOCK_KEY = "GpsLocationProvider"; private final PowerManager.WakeLock mWakeLock; - // bitfield of pending messages to our Handler - // used only for messages that cannot have multiple instances queued - private int mPendingMessageBits; - // separate counter for ADD_LISTENER and REMOVE_LISTENER messages, - // which might have multiple instances queued - private int mPendingListenerMessages; // Alarms private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP"; @@ -287,22 +303,18 @@ public class GpsLocationProvider implements LocationProviderInterface { private final PendingIntent mTimeoutIntent; private final IBatteryStats mBatteryStats; - private final SparseIntArray mClientUids = new SparseIntArray(); - // how often to request NTP time, in milliseconds - // current setting 24 hours - private static final long NTP_INTERVAL = 24*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; + // only modified on handler thread + private int[] mClientUids = new int[0]; private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() { + @Override public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException { if (listener == null) { throw new NullPointerException("listener is null in addGpsStatusListener"); } - synchronized(mListeners) { + synchronized (mListeners) { IBinder binder = listener.asBinder(); int size = mListeners.size(); for (int i = 0; i < size; i++) { @@ -319,12 +331,13 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + @Override public void removeGpsStatusListener(IGpsStatusListener listener) { if (listener == null) { throw new NullPointerException("listener is null in addGpsStatusListener"); } - synchronized(mListeners) { + synchronized (mListeners) { IBinder binder = listener.asBinder(); Listener l = null; int size = mListeners.size(); @@ -353,7 +366,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (action.equals(ALARM_WAKEUP)) { if (DEBUG) Log.d(TAG, "ALARM_WAKEUP"); - startNavigating(false); + startNavigating(); } else if (action.equals(ALARM_TIMEOUT)) { if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); hibernate(); @@ -361,6 +374,22 @@ public class GpsLocationProvider implements LocationProviderInterface { checkSmsSuplInit(intent); } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) { checkWapSuplInit(intent); + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + int networkState; + if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) { + networkState = LocationProvider.TEMPORARILY_UNAVAILABLE; + } else { + networkState = LocationProvider.AVAILABLE; + } + + // retrieve NetworkInfo result for this UID + NetworkInfo info = + intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); + ConnectivityManager connManager = (ConnectivityManager) + mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + info = connManager.getNetworkInfo(info.getType()); + + updateNetworkState(networkState, info); } } }; @@ -382,10 +411,10 @@ public class GpsLocationProvider implements LocationProviderInterface { return native_is_supported(); } - public GpsLocationProvider(Context context, ILocationManager locationManager) { + public GpsLocationProvider(Context context, ILocationManager ilocationManager) { mContext = context; mNtpTime = NtpTrustedTime.getInstance(context); - mLocationManager = locationManager; + mILocationManager = ilocationManager; mNIHandler = new GpsNetInitiatedHandler(context); mLocation.setExtras(mLocationExtras); @@ -393,7 +422,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // Create a wake lock PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); - mWakeLock.setReferenceCounted(false); + mWakeLock.setReferenceCounted(true); mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0); @@ -473,16 +502,14 @@ public class GpsLocationProvider implements LocationProviderInterface { /** * Returns the name of this provider. */ + @Override public String getName() { return LocationManager.GPS_PROVIDER; } - /** - * Returns true if the provider requires access to a - * data network (e.g., the Internet), false otherwise. - */ - public boolean requiresNetwork() { - return true; + @Override + public ProviderProperties getProperties() { + return PROPERTIES; } public void updateNetworkState(int state, NetworkInfo info) { @@ -516,7 +543,7 @@ public class GpsLocationProvider implements LocationProviderInterface { String apnName = info.getExtraInfo(); if (mNetworkAvailable) { if (apnName == null) { - /* Assign a dummy value in the case of C2K as otherwise we will have a runtime + /* Assign a dummy value in the case of C2K as otherwise we will have a runtime exception in the following call to native_agps_data_conn_open*/ apnName = "dummy-apn"; } @@ -613,18 +640,11 @@ public class GpsLocationProvider implements LocationProviderInterface { // try again later // since this is delayed and not urgent we do not hold a wake lock here mHandler.removeMessages(DOWNLOAD_XTRA_DATA); - mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), RETRY_INTERVAL); + mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), + RETRY_INTERVAL); } } - /** - * This is called to inform us when another location provider returns a location. - * Someday we might use this for network location injection to aid the GPS - */ - public void updateLocation(Location location) { - sendMessage(UPDATE_LOCATION, 0, location); - } - private void handleUpdateLocation(Location location) { if (location.hasAccuracy()) { native_inject_location(location.getLatitude(), location.getLongitude(), @@ -633,107 +653,26 @@ public class GpsLocationProvider implements LocationProviderInterface { } /** - * Returns true if the provider requires access to a - * satellite-based positioning system (e.g., GPS), false - * otherwise. - */ - 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. - */ - 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. - */ - 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. - */ - 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. - */ - 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. - */ - 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_*. - */ - public int getPowerRequirement() { - return Criteria.POWER_HIGH; - } - - /** - * Returns true if this provider meets the given criteria, - * false otherwise. - */ - public boolean meetsCriteria(Criteria criteria) { - return (criteria.getPowerRequirement() != Criteria.POWER_LOW); - } - - /** - * Returns the horizontal accuracy of this provider - * - * @return the accuracy of location from this provider, as one - * of the constants Criteria.ACCURACY_*. - */ - public int getAccuracy() { - return Criteria.ACCURACY_FINE; - } - - /** * Enables this provider. When enabled, calls to getStatus() * must be handled. Hardware may be started up * when the provider is enabled. */ + @Override public void enable() { - synchronized (mHandler) { - sendMessage(ENABLE, 1, null); - } + sendMessage(ENABLE, 1, null); } private void handleEnable() { if (DEBUG) Log.d(TAG, "handleEnable"); - if (mEnabled) return; - mEnabled = native_init(); - if (mEnabled) { + synchronized (mLock) { + if (mEnabled) return; + mEnabled = true; + } + + boolean enabled = native_init(); + + if (enabled) { mSupportsXtra = native_supports_xtra(); if (mSuplServerHost != null) { native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); @@ -742,6 +681,9 @@ public class GpsLocationProvider implements LocationProviderInterface { native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); } } else { + synchronized (mLock) { + mEnabled = false; + } Log.w(TAG, "Failed to enable location provider"); } } @@ -751,27 +693,35 @@ public class GpsLocationProvider implements LocationProviderInterface { * need not be handled. Hardware may be shut * down while the provider is disabled. */ + @Override public void disable() { - synchronized (mHandler) { - sendMessage(ENABLE, 0, null); - } + sendMessage(ENABLE, 0, null); } private void handleDisable() { if (DEBUG) Log.d(TAG, "handleDisable"); - if (!mEnabled) return; - mEnabled = false; + synchronized (mLock) { + if (!mEnabled) return; + mEnabled = false; + } + stopNavigating(); + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); // do this before releasing wakelock native_cleanup(); } + @Override public boolean isEnabled() { - return mEnabled; + synchronized (mLock) { + return mEnabled; + } } + @Override public int getStatus(Bundle extras) { if (extras != null) { extras.putInt("satellites", mSvCount); @@ -788,93 +738,69 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + @Override public long getStatusUpdateTime() { return mStatusUpdateTime; } - public void enableLocationTracking(boolean enable) { - // FIXME - should set a flag here to avoid race conditions with single shot request - synchronized (mHandler) { - sendMessage(ENABLE_TRACKING, (enable ? 1 : 0), null); - } + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + sendMessage(SET_REQUEST, 0, new GpsRequest(request, source)); } - private void handleEnableLocationTracking(boolean enable) { - if (enable) { - mTTFF = 0; - mLastFixTime = 0; - startNavigating(false); - } else { - if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { - mAlarmManager.cancel(mWakeupIntent); - mAlarmManager.cancel(mTimeoutIntent); - } - stopNavigating(); - } - } + private void handleSetRequest(ProviderRequest request, WorkSource source) { + if (DEBUG) Log.d(TAG, "setRequest " + request); - public boolean requestSingleShotFix() { - if (mStarted) { - // cannot do single shot if already navigating - return false; - } - synchronized (mHandler) { - mHandler.removeMessages(REQUEST_SINGLE_SHOT); - Message m = Message.obtain(mHandler, REQUEST_SINGLE_SHOT); - mHandler.sendMessage(m); - } - return true; - } - private void handleRequestSingleShot() { - mTTFF = 0; - mLastFixTime = 0; - startNavigating(true); - } - public void setMinTime(long minTime, WorkSource ws) { - if (DEBUG) Log.d(TAG, "setMinTime " + minTime); - - if (minTime >= 0) { - mFixInterval = (int)minTime; + if (request.reportLocation) { + // update client uids + int[] uids = new int[source.size()]; + for (int i=0; i < source.size(); i++) { + uids[i] = source.get(i); + } + updateClientUids(uids); + mFixInterval = (int) request.interval; + + // check for overflow + if (mFixInterval != request.interval) { + Log.w(TAG, "interval overflow: " + request.interval); + mFixInterval = Integer.MAX_VALUE; + } + + // apply request to GPS engine if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { + // change period if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, mFixInterval, 0, 0)) { Log.e(TAG, "set_position_mode failed in setMinTime()"); } + } else if (!mStarted) { + // start GPS + startNavigating(); } - } - } - - public String getInternalState() { - StringBuilder s = new StringBuilder(); - s.append(" mFixInterval=").append(mFixInterval).append("\n"); - s.append(" mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities)).append(" ("); - if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHED "); - if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB "); - if (hasCapability(GPS_CAPABILITY_MSA)) s.append("MSA "); - if (hasCapability(GPS_CAPABILITY_SINGLE_SHOT)) s.append("SINGLE_SHOT "); - if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) s.append("ON_DEMAND_TIME "); - s.append(")\n"); + } else { + updateClientUids(new int[0]); - s.append(native_get_internal_state()); - return s.toString(); + stopNavigating(); + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); + } } private final class Listener implements IBinder.DeathRecipient { final IGpsStatusListener mListener; - - int mSensors = 0; - + Listener(IGpsStatusListener listener) { mListener = listener; } - + + @Override public void binderDied() { if (DEBUG) Log.d(TAG, "GPS status listener died"); - synchronized(mListeners) { + synchronized (mListeners) { mListeners.remove(this); } if (mListener != null) { @@ -883,64 +809,47 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - public void addListener(int uid) { - synchronized (mWakeLock) { - mPendingListenerMessages++; - mWakeLock.acquire(); - Message m = Message.obtain(mHandler, ADD_LISTENER); - m.arg1 = uid; - mHandler.sendMessage(m); - } - } - - private void handleAddListener(int uid) { - synchronized(mListeners) { - if (mClientUids.indexOfKey(uid) >= 0) { - // Shouldn't be here -- already have this uid. - Log.w(TAG, "Duplicate add listener for uid " + uid); - return; + private void updateClientUids(int[] uids) { + // Find uid's that were not previously tracked + for (int uid1 : uids) { + boolean newUid = true; + for (int uid2 : mClientUids) { + if (uid1 == uid2) { + newUid = false; + break; + } } - mClientUids.put(uid, 0); - if (mNavigating) { + if (newUid) { try { - mBatteryStats.noteStartGps(uid); + mBatteryStats.noteStartGps(uid1); } catch (RemoteException e) { - Log.w(TAG, "RemoteException in addListener"); + Log.w(TAG, "RemoteException", e); } } } - } - - public void removeListener(int uid) { - synchronized (mWakeLock) { - mPendingListenerMessages++; - mWakeLock.acquire(); - Message m = Message.obtain(mHandler, REMOVE_LISTENER); - m.arg1 = uid; - mHandler.sendMessage(m); - } - } - private void handleRemoveListener(int uid) { - synchronized(mListeners) { - if (mClientUids.indexOfKey(uid) < 0) { - // Shouldn't be here -- don't have this uid. - Log.w(TAG, "Unneeded remove listener for uid " + uid); - return; + // Find uid'd that were tracked but have now disappeared + for (int uid1 : mClientUids) { + boolean oldUid = true; + for (int uid2 : uids) { + if (uid1 == uid2) { + oldUid = false; + break; + } } - mClientUids.delete(uid); - if (mNavigating) { + if (oldUid) { try { - mBatteryStats.noteStopGps(uid); + mBatteryStats.noteStopGps(uid1); } catch (RemoteException e) { - Log.w(TAG, "RemoteException in removeListener"); + Log.w(TAG, "RemoteException", e); } } } } + @Override public boolean sendExtraCommand(String command, Bundle extras) { - + long identity = Binder.clearCallingIdentity(); boolean result = false; @@ -957,7 +866,7 @@ public class GpsLocationProvider implements LocationProviderInterface { } else { Log.w(TAG, "sendExtraCommand: unknown command " + command); } - + Binder.restoreCallingIdentity(identity); return result; } @@ -992,18 +901,17 @@ public class GpsLocationProvider implements LocationProviderInterface { return false; } - private void startNavigating(boolean singleShot) { + private void startNavigating() { if (!mStarted) { if (DEBUG) Log.d(TAG, "startNavigating"); + mTimeToFirstFix = 0; + mLastFixTime = 0; mStarted = true; - mSingleShot = singleShot; mPositionMode = GPS_POSITION_MODE_STANDALONE; if (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { - if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) { - mPositionMode = GPS_POSITION_MODE_MS_ASSISTED; - } else if (hasCapability(GPS_CAPABILITY_MSB)) { + if (hasCapability(GPS_CAPABILITY_MSB)) { mPositionMode = GPS_POSITION_MODE_MS_BASED; } } @@ -1039,9 +947,8 @@ public class GpsLocationProvider implements LocationProviderInterface { if (DEBUG) Log.d(TAG, "stopNavigating"); if (mStarted) { mStarted = false; - mSingleShot = false; native_stop(); - mTTFF = 0; + mTimeToFirstFix = 0; mLastFixTime = 0; mLocationFlags = LOCATION_INVALID; @@ -1056,8 +963,7 @@ public class GpsLocationProvider implements LocationProviderInterface { mAlarmManager.cancel(mTimeoutIntent); mAlarmManager.cancel(mWakeupIntent); long now = SystemClock.elapsedRealtime(); - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + mFixInterval, mWakeupIntent); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, mWakeupIntent); } private boolean hasCapability(int capability) { @@ -1105,7 +1011,7 @@ public class GpsLocationProvider implements LocationProviderInterface { mLocation.setExtras(mLocationExtras); try { - mLocationManager.reportLocation(mLocation, false); + mILocationManager.reportLocation(mLocation, false); } catch (RemoteException e) { Log.e(TAG, "RemoteException calling reportLocation"); } @@ -1113,17 +1019,17 @@ public class GpsLocationProvider implements LocationProviderInterface { mLastFixTime = System.currentTimeMillis(); // report time to first fix - if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { - mTTFF = (int)(mLastFixTime - mFixRequestTime); - if (DEBUG) Log.d(TAG, "TTFF: " + mTTFF); + if (mTimeToFirstFix == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mTimeToFirstFix = (int)(mLastFixTime - mFixRequestTime); + if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix); // notify status listeners - synchronized(mListeners) { + synchronized (mListeners) { int size = mListeners.size(); for (int i = 0; i < size; i++) { Listener listener = mListeners.get(i); try { - listener.mListener.onFirstFix(mTTFF); + listener.mListener.onFirstFix(mTimeToFirstFix); } catch (RemoteException e) { Log.w(TAG, "RemoteException in stopNavigating"); mListeners.remove(listener); @@ -1134,9 +1040,6 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - if (mSingleShot) { - stopNavigating(); - } if (mStarted && mStatus != LocationProvider.AVAILABLE) { // we want to time out if we do not receive a fix // within the time out and we are requesting infrequent fixes @@ -1164,7 +1067,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private void reportStatus(int status) { if (DEBUG) Log.v(TAG, "reportStatus status: " + status); - synchronized(mListeners) { + synchronized (mListeners) { boolean wasNavigating = mNavigating; switch (status) { @@ -1202,20 +1105,6 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - try { - // update battery stats - for (int i=mClientUids.size() - 1; i >= 0; i--) { - int uid = mClientUids.keyAt(i); - if (mNavigating) { - mBatteryStats.noteStartGps(uid); - } else { - mBatteryStats.noteStopGps(uid); - } - } - } catch (RemoteException e) { - Log.w(TAG, "RemoteException in reportStatus"); - } - // send an intent to notify that the GPS has been enabled or disabled. Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating); @@ -1230,15 +1119,15 @@ public class GpsLocationProvider implements LocationProviderInterface { private void reportSvStatus() { int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks); - - synchronized(mListeners) { + + 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]); + 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); @@ -1254,7 +1143,7 @@ public class GpsLocationProvider implements LocationProviderInterface { " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK])); for (int i = 0; i < svCount; i++) { Log.v(TAG, "sv: " + mSvs[i] + - " snr: " + (float)mSnrs[i]/10 + + " snr: " + mSnrs[i]/10 + " elev: " + mSvElevations[i] + " azimuth: " + mSvAzimuths[i] + ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " E") + @@ -1342,7 +1231,7 @@ public class GpsLocationProvider implements LocationProviderInterface { * called from native code to report NMEA data received */ private void reportNmea(long timestamp) { - synchronized(mListeners) { + synchronized (mListeners) { int size = mListeners.size(); if (size > 0) { // don't bother creating the String if we have no listeners @@ -1389,19 +1278,18 @@ public class GpsLocationProvider implements LocationProviderInterface { //============================================================= private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { // Sends a response for an NI reqeust to HAL. + @Override public boolean sendNiResponse(int notificationId, int userResponse) { // TODO Add Permission check - StringBuilder extrasBuf = new StringBuilder(); - if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId + ", response: " + userResponse); native_send_ni_response(notificationId, userResponse); return true; } }; - + public INetInitiatedListener getNetInitiatedListener() { return mNetInitiatedListener; } @@ -1550,16 +1438,9 @@ public class GpsLocationProvider implements LocationProviderInterface { } private void sendMessage(int message, int arg, Object obj) { - // hold a wake lock while messages are pending - synchronized (mWakeLock) { - mPendingMessageBits |= (1 << message); - mWakeLock.acquire(); - mHandler.removeMessages(message); - Message m = Message.obtain(mHandler, message); - m.arg1 = arg; - m.obj = obj; - mHandler.sendMessage(m); - } + // hold a wake lock until this message is delivered + mWakeLock.acquire(); + mHandler.obtainMessage(message, arg, 1, obj).sendToTarget(); } private final class ProviderHandler extends Handler { @@ -1574,11 +1455,9 @@ public class GpsLocationProvider implements LocationProviderInterface { handleDisable(); } break; - case ENABLE_TRACKING: - handleEnableLocationTracking(msg.arg1 == 1); - break; - case REQUEST_SINGLE_SHOT: - handleRequestSingleShot(); + case SET_REQUEST: + GpsRequest gpsRequest = (GpsRequest) msg.obj; + handleSetRequest(gpsRequest.request, gpsRequest.source); break; case UPDATE_NETWORK_STATE: handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj); @@ -1594,22 +1473,10 @@ public class GpsLocationProvider implements LocationProviderInterface { case UPDATE_LOCATION: handleUpdateLocation((Location)msg.obj); break; - case ADD_LISTENER: - handleAddListener(msg.arg1); - break; - case REMOVE_LISTENER: - handleRemoveListener(msg.arg1); - break; } - // release wake lock if no messages are pending - synchronized (mWakeLock) { - mPendingMessageBits &= ~(1 << message); - if (message == ADD_LISTENER || message == REMOVE_LISTENER) { - mPendingListenerMessages--; - } - if (mPendingMessageBits == 0 && mPendingListenerMessages == 0) { - mWakeLock.release(); - } + if (msg.arg2 == 1) { + // wakelock was taken for this message, release it + mWakeLock.release(); } } }; @@ -1620,17 +1487,39 @@ public class GpsLocationProvider implements LocationProviderInterface { super("GpsLocationProvider"); } + @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); initialize(); Looper.prepare(); + + LocationManager locManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); mHandler = new ProviderHandler(); // signal when we are initialized and ready to go mInitializedLatch.countDown(); + locManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, + 0, 0, new NetworkLocationListener(), Looper.myLooper()); Looper.loop(); } } + private final class NetworkLocationListener implements LocationListener { + @Override + public void onLocationChanged(Location location) { + // this callback happens on mHandler looper + if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) { + handleUpdateLocation(location); + } + } + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { } + @Override + public void onProviderEnabled(String provider) { } + @Override + public void onProviderDisabled(String provider) { } + } + private String getSelectedApn() { Uri uri = Uri.parse("content://telephony/carriers/preferapn"); String apn = null; @@ -1650,6 +1539,22 @@ public class GpsLocationProvider implements LocationProviderInterface { return apn; } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + StringBuilder s = new StringBuilder(); + s.append(" mFixInterval=").append(mFixInterval).append("\n"); + s.append(" mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities)).append(" ("); + if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHED "); + if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB "); + if (hasCapability(GPS_CAPABILITY_MSA)) s.append("MSA "); + if (hasCapability(GPS_CAPABILITY_SINGLE_SHOT)) s.append("SINGLE_SHOT "); + if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) s.append("ON_DEMAND_TIME "); + s.append(")\n"); + + s.append(native_get_internal_state()); + pw.append(s); + } + // for GPS SV statistics private static final int MAX_SVS = 32; private static final int EPHEMERIS_MASK = 0; diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java index 858a582..6f09232 100644 --- a/services/java/com/android/server/location/LocationProviderInterface.java +++ b/services/java/com/android/server/location/LocationProviderInterface.java @@ -16,42 +16,33 @@ package com.android.server.location; -import android.location.Criteria; -import android.location.Location; -import android.net.NetworkInfo; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + + import android.os.Bundle; import android.os.WorkSource; /** * Location Manager's interface for location providers. - * - * {@hide} + * @hide */ public interface LocationProviderInterface { - String getName(); - boolean requiresNetwork(); - boolean requiresSatellite(); - boolean requiresCell(); - boolean hasMonetaryCost(); - boolean supportsAltitude(); - boolean supportsSpeed(); - boolean supportsBearing(); - int getPowerRequirement(); - boolean meetsCriteria(Criteria criteria); - int getAccuracy(); - boolean isEnabled(); - void enable(); - void disable(); - int getStatus(Bundle extras); - long getStatusUpdateTime(); - void enableLocationTracking(boolean enable); - /* returns false if single shot is not supported */ - boolean requestSingleShotFix(); - String getInternalState(); - void setMinTime(long minTime, WorkSource ws); - void updateNetworkState(int state, NetworkInfo info); - void updateLocation(Location location); - boolean sendExtraCommand(String command, Bundle extras); - void addListener(int uid); - void removeListener(int uid); + public String getName(); + + public void enable(); + public void disable(); + public boolean isEnabled(); + public void setRequest(ProviderRequest request, WorkSource source); + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args); + + // --- deprecated (but still supported) --- + public ProviderProperties getProperties(); + public int getStatus(Bundle extras); + public long getStatusUpdateTime(); + public boolean sendExtraCommand(String command, Bundle extras); } diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java index a227ab6..7faf72c 100644 --- a/services/java/com/android/server/location/LocationProviderProxy.java +++ b/services/java/com/android/server/location/LocationProviderProxy.java @@ -16,458 +16,272 @@ package com.android.server.location; -import android.content.ComponentName; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.location.Criteria; -import android.location.ILocationProvider; -import android.location.Location; -import android.net.NetworkInfo; +import android.location.LocationProvider; import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; -import com.android.internal.location.DummyLocationProvider; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ILocationProvider; +import com.android.internal.location.ProviderRequest; +import com.android.server.LocationManagerService; +import com.android.server.ServiceWatcher; /** - * A class for proxying location providers implemented as services. - * - * {@hide} + * Proxy for ILocationProvider implementations. */ public class LocationProviderProxy implements LocationProviderInterface { - private static final String TAG = "LocationProviderProxy"; - - public static final String SERVICE_ACTION = - "com.android.location.service.NetworkLocationProvider"; + private static final boolean D = LocationManagerService.D; private final Context mContext; private final String mName; - private final Intent mIntent; - private final Handler mHandler; - private final Object mMutex = new Object(); // synchronizes access to non-final members - private Connection mServiceConnection; // never null after ctor + private final ServiceWatcher mServiceWatcher; + + private Object mLock = new Object(); - // cached values set by the location manager - private boolean mLocationTracking = false; + // cached values set by the location manager, synchronized on mLock + private ProviderProperties mProperties; private boolean mEnabled = false; - private long mMinTime = -1; - private WorkSource mMinTimeSource = new WorkSource(); - private int mNetworkState; - private NetworkInfo mNetworkInfo; - - // constructor for proxying location providers implemented in a separate service - public LocationProviderProxy(Context context, String name, String packageName, - Handler handler) { + private ProviderRequest mRequest = null; + private WorkSource mWorksource = new WorkSource(); + + public static LocationProviderProxy createAndBind(Context context, String name, String action, + List<String> initialPackageNames, Handler handler) { + LocationProviderProxy proxy = new LocationProviderProxy(context, name, action, + initialPackageNames, handler); + if (proxy.bind()) { + return proxy; + } else { + return null; + } + } + + private LocationProviderProxy(Context context, String name, String action, + List<String> initialPackageNames, Handler handler) { mContext = context; mName = name; - mIntent = new Intent(SERVICE_ACTION); - mHandler = handler; - reconnect(packageName); + mServiceWatcher = new ServiceWatcher(mContext, TAG, action, initialPackageNames, + mNewServiceWork, handler); } - /** Bind to service. Will reconnect if already connected */ - public void reconnect(String packageName) { - synchronized (mMutex) { - if (mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - } - mServiceConnection = new Connection(); - mIntent.setPackage(packageName); - mContext.bindService(mIntent, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | - Context.BIND_ALLOW_OOM_MANAGEMENT); - } + private boolean bind () { + return mServiceWatcher.start(); } - private class Connection implements ServiceConnection, Runnable { - - private ILocationProvider mProvider; - - // for caching requiresNetwork, requiresSatellite, etc. - private DummyLocationProvider mCachedAttributes; // synchronized by mMutex + private ILocationProvider getService() { + return ILocationProvider.Stub.asInterface(mServiceWatcher.getBinder()); + } - public void onServiceConnected(ComponentName className, IBinder service) { - synchronized (this) { - mProvider = ILocationProvider.Stub.asInterface(service); - if (mProvider != null) { - mHandler.post(this); - } - } - } + public String getConnectedPackageName() { + return mServiceWatcher.getBestPackageName(); + } - public void onServiceDisconnected(ComponentName className) { - synchronized (this) { - mProvider = null; + /** + * Work to apply current state to a newly connected provider. + * Remember we can switch the service that implements a providers + * at run-time, so need to apply current state. + */ + private Runnable mNewServiceWork = new Runnable() { + @Override + public void run() { + if (D) Log.d(TAG, "applying state to connected service"); + + boolean enabled; + ProviderProperties properties = null; + ProviderRequest request; + WorkSource source; + ILocationProvider service; + synchronized (mLock) { + enabled = mEnabled; + request = mRequest; + source = mWorksource; + service = getService(); } - } - public synchronized ILocationProvider getProvider() { - return mProvider; - } - - public synchronized DummyLocationProvider getCachedAttributes() { - return mCachedAttributes; - } + if (service == null) return; - public void run() { - synchronized (mMutex) { - if (mServiceConnection != this) { - // This ServiceConnection no longer the one we want to bind to. - return; - } - ILocationProvider provider = getProvider(); - if (provider == null) { - return; + try { + // load properties from provider + properties = service.getProperties(); + if (properties == null) { + Log.e(TAG, mServiceWatcher.getBestPackageName() + + " has invalid locatino provider properties"); } - // resend previous values from the location manager if the service has restarted - try { - if (mEnabled) { - provider.enable(); - } - if (mLocationTracking) { - provider.enableLocationTracking(true); + // apply current state to new service + if (enabled) { + service.enable(); + if (request != null) { + service.setRequest(request, source); } - if (mMinTime >= 0) { - provider.setMinTime(mMinTime, mMinTimeSource); - } - if (mNetworkInfo != null) { - provider.updateNetworkState(mNetworkState, mNetworkInfo); - } - } catch (RemoteException e) { } + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); + } - // init cache of parameters - if (mCachedAttributes == null) { - try { - mCachedAttributes = new DummyLocationProvider(mName, null); - mCachedAttributes.setRequiresNetwork(provider.requiresNetwork()); - mCachedAttributes.setRequiresSatellite(provider.requiresSatellite()); - mCachedAttributes.setRequiresCell(provider.requiresCell()); - mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost()); - mCachedAttributes.setSupportsAltitude(provider.supportsAltitude()); - mCachedAttributes.setSupportsSpeed(provider.supportsSpeed()); - mCachedAttributes.setSupportsBearing(provider.supportsBearing()); - mCachedAttributes.setPowerRequirement(provider.getPowerRequirement()); - mCachedAttributes.setAccuracy(provider.getAccuracy()); - } catch (RemoteException e) { - mCachedAttributes = null; - } - } + synchronized (mLock) { + mProperties = properties; } } }; + @Override public String getName() { return mName; } - private DummyLocationProvider getCachedAttributes() { - synchronized (mMutex) { - return mServiceConnection.getCachedAttributes(); - } - } - - public boolean requiresNetwork() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresNetwork(); - } else { - return false; - } - } - - public boolean requiresSatellite() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresSatellite(); - } else { - return false; - } - } - - public boolean requiresCell() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresCell(); - } else { - return false; - } - } - - public boolean hasMonetaryCost() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.hasMonetaryCost(); - } else { - return false; - } - } - - public boolean supportsAltitude() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsAltitude(); - } else { - return false; - } - } - - public boolean supportsSpeed() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsSpeed(); - } else { - return false; - } - } - - public boolean supportsBearing() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsBearing(); - } else { - return false; - } - } - - public int getPowerRequirement() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.getPowerRequirement(); - } else { - return -1; + @Override + public ProviderProperties getProperties() { + synchronized (mLock) { + return mProperties; } } - public int getAccuracy() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.getAccuracy(); - } else { - return -1; - } - } - - public boolean meetsCriteria(Criteria criteria) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - return provider.meetsCriteria(criteria); - } catch (RemoteException e) { - } - } - } - // default implementation if we lost connection to the provider - 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; - } - + @Override public void enable() { - synchronized (mMutex) { + synchronized (mLock) { mEnabled = true; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.enable(); - } catch (RemoteException e) { - } - } } - } + ILocationProvider service = getService(); + if (service == null) return; - public void disable() { - synchronized (mMutex) { - mEnabled = false; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.disable(); - } catch (RemoteException e) { - } - } + try { + service.enable(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public boolean isEnabled() { - synchronized (mMutex) { - return mEnabled; + @Override + public void disable() { + synchronized (mLock) { + mEnabled = false; } - } + ILocationProvider service = getService(); + if (service == null) return; - public int getStatus(Bundle extras) { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); + try { + service.disable(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } - if (provider != null) { - try { - return provider.getStatus(extras); - } catch (RemoteException e) { - } - } - return 0; } - public long getStatusUpdateTime() { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } - if (provider != null) { - try { - return provider.getStatusUpdateTime(); - } catch (RemoteException e) { - } - } - return 0; - } - - public String getInternalState() { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } - if (provider != null) { - try { - return provider.getInternalState(); - } catch (RemoteException e) { - Log.e(TAG, "getInternalState failed", e); - } + @Override + public boolean isEnabled() { + synchronized (mLock) { + return mEnabled; } - return null; } - public boolean isLocationTracking() { - synchronized (mMutex) { - return mLocationTracking; + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + synchronized (mLock) { + mRequest = request; + mWorksource = source; } - } + ILocationProvider service = getService(); + if (service == null) return; - public void enableLocationTracking(boolean enable) { - synchronized (mMutex) { - mLocationTracking = enable; - if (!enable) { - mMinTime = -1; - mMinTimeSource.clear(); - } - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.enableLocationTracking(enable); - } catch (RemoteException e) { - } - } + try { + service.setRequest(request, source); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public boolean requestSingleShotFix() { - return false; - } - - public long getMinTime() { - synchronized (mMutex) { - return mMinTime; + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.append("REMOTE SERVICE"); + pw.append(" name=").append(mName); + pw.append(" pkg=").append(mServiceWatcher.getBestPackageName()); + pw.append(" version=").append("" + mServiceWatcher.getBestVersion()); + pw.append('\n'); + + ILocationProvider service = getService(); + if (service == null) { + pw.println("service down (null)"); + return; + } + pw.flush(); + + try { + service.asBinder().dump(fd, args); + } catch (RemoteException e) { + pw.println("service down (RemoteException)"); + Log.w(TAG, e); + } catch (Exception e) { + pw.println("service down (Exception)"); + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public void setMinTime(long minTime, WorkSource ws) { - synchronized (mMutex) { - mMinTime = minTime; - mMinTimeSource.set(ws); - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.setMinTime(minTime, ws); - } catch (RemoteException e) { - } - } - } + @Override + public int getStatus(Bundle extras) { + ILocationProvider service = getService(); + if (service == null) return LocationProvider.TEMPORARILY_UNAVAILABLE; + + try { + return service.getStatus(extras); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); + } + return LocationProvider.TEMPORARILY_UNAVAILABLE; } - public void updateNetworkState(int state, NetworkInfo info) { - synchronized (mMutex) { - mNetworkState = state; - mNetworkInfo = info; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.updateNetworkState(state, info); - } catch (RemoteException e) { - } - } - } - } + @Override + public long getStatusUpdateTime() { + ILocationProvider service = getService(); + if (service == null) return 0; - public void updateLocation(Location location) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.updateLocation(location); - } catch (RemoteException e) { - } - } + try { + return service.getStatusUpdateTime(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } + return 0; } + @Override public boolean sendExtraCommand(String command, Bundle extras) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - return provider.sendExtraCommand(command, extras); - } catch (RemoteException e) { - } - } - } - return false; - } + ILocationProvider service = getService(); + if (service == null) return false; - public void addListener(int uid) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.addListener(uid); - } catch (RemoteException e) { - } - } - } - } - - public void removeListener(int uid) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.removeListener(uid); - } catch (RemoteException e) { - } - } + try { + return service.sendExtraCommand(command, extras); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } + return false; } -} + } diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java index 09d799f..36c43ff 100644 --- a/services/java/com/android/server/location/MockProvider.java +++ b/services/java/com/android/server/location/MockProvider.java @@ -20,15 +20,19 @@ import android.location.Criteria; import android.location.ILocationManager; import android.location.Location; import android.location.LocationProvider; -import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; import android.util.PrintWriterPrinter; + +import java.io.FileDescriptor; import java.io.PrintWriter; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + /** * A mock location provider used by LocationManagerService to implement test providers. * @@ -36,60 +40,56 @@ import java.io.PrintWriter; */ public class MockProvider implements LocationProviderInterface { private final String mName; + private final ProviderProperties mProperties; private final ILocationManager mLocationManager; - private final boolean mRequiresNetwork; - private final boolean mRequiresSatellite; - private final boolean mRequiresCell; - private final boolean mHasMonetaryCost; - private final boolean mSupportsAltitude; - private final boolean mSupportsSpeed; - private final boolean mSupportsBearing; - private final int mPowerRequirement; - private final int mAccuracy; + private final Location mLocation; + private final Bundle mExtras = new Bundle(); + private int mStatus; private long mStatusUpdateTime; - private final Bundle mExtras = new Bundle(); private boolean mHasLocation; private boolean mHasStatus; private boolean mEnabled; private static final String TAG = "MockProvider"; - public MockProvider(String name, ILocationManager locationManager, - boolean requiresNetwork, boolean requiresSatellite, - boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, - boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + public MockProvider(String name, ILocationManager locationManager, + ProviderProperties properties) { + if (properties == null) throw new NullPointerException("properties is null"); + mName = name; mLocationManager = locationManager; - mRequiresNetwork = requiresNetwork; - mRequiresSatellite = requiresSatellite; - mRequiresCell = requiresCell; - mHasMonetaryCost = hasMonetaryCost; - mSupportsAltitude = supportsAltitude; - mSupportsBearing = supportsBearing; - mSupportsSpeed = supportsSpeed; - mPowerRequirement = powerRequirement; - mAccuracy = accuracy; + mProperties = properties; mLocation = new Location(name); } + @Override public String getName() { return mName; } + @Override + public ProviderProperties getProperties() { + return mProperties; + } + + @Override public void disable() { mEnabled = false; } + @Override public void enable() { mEnabled = true; } + @Override public boolean isEnabled() { return mEnabled; } + @Override public int getStatus(Bundle extras) { if (mHasStatus) { extras.clear(); @@ -100,75 +100,20 @@ public class MockProvider implements LocationProviderInterface { } } + @Override public long getStatusUpdateTime() { return mStatusUpdateTime; } - public int getAccuracy() { - return mAccuracy; - } - - public int getPowerRequirement() { - return mPowerRequirement; - } - - public boolean hasMonetaryCost() { - return mHasMonetaryCost; - } - - public boolean requiresCell() { - return mRequiresCell; - } - - public boolean requiresNetwork() { - return mRequiresNetwork; - } - - public boolean requiresSatellite() { - return mRequiresSatellite; - } - - public boolean supportsAltitude() { - return mSupportsAltitude; - } - - public boolean supportsBearing() { - return mSupportsBearing; - } - - public boolean supportsSpeed() { - return mSupportsSpeed; - } - - public boolean meetsCriteria(Criteria criteria) { - if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && - (criteria.getAccuracy() < mAccuracy)) { - return false; - } - int criteriaPower = criteria.getPowerRequirement(); - if ((criteriaPower != Criteria.NO_REQUIREMENT) && - (criteriaPower < mPowerRequirement)) { - return false; - } - if (criteria.isAltitudeRequired() && !mSupportsAltitude) { - return false; - } - if (criteria.isSpeedRequired() && !mSupportsSpeed) { - return false; - } - if (criteria.isBearingRequired() && !mSupportsBearing) { - return false; - } - return true; - } - public void setLocation(Location l) { mLocation.set(l); mHasLocation = true; - try { - mLocationManager.reportLocation(mLocation, false); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling reportLocation"); + if (mEnabled) { + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } } } @@ -191,34 +136,9 @@ public class MockProvider implements LocationProviderInterface { mStatusUpdateTime = 0; } - public String getInternalState() { - return null; - } - - public void enableLocationTracking(boolean enable) { - } - - public boolean requestSingleShotFix() { - return false; - } - - public void setMinTime(long minTime, WorkSource ws) { - } - - public void updateNetworkState(int state, NetworkInfo info) { - } - - public void updateLocation(Location location) { - } - - public boolean sendExtraCommand(String command, Bundle extras) { - return false; - } - - public void addListener(int uid) { - } - - public void removeListener(int uid) { + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + dump(pw, ""); } public void dump(PrintWriter pw, String prefix) { @@ -231,4 +151,12 @@ public class MockProvider implements LocationProviderInterface { pw.println(prefix + "mStatusUpdateTime=" + mStatusUpdateTime); pw.println(prefix + "mExtras=" + mExtras); } + + @Override + public void setRequest(ProviderRequest request, WorkSource source) { } + + @Override + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } } diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java index ea0d1b0..0ce21b7 100644 --- a/services/java/com/android/server/location/PassiveProvider.java +++ b/services/java/com/android/server/location/PassiveProvider.java @@ -16,17 +16,23 @@ package com.android.server.location; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + import android.location.Criteria; import android.location.ILocationManager; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; -import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; + /** * A passive location provider reports locations received from other providers * for clients that want to listen passively without actually triggering @@ -35,103 +41,63 @@ import android.util.Log; * {@hide} */ public class PassiveProvider implements LocationProviderInterface { - private static final String TAG = "PassiveProvider"; + private static final ProviderProperties PROPERTIES = new ProviderProperties( + false, false, false, false, false, false, false, + Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); + private final ILocationManager mLocationManager; - private boolean mTracking; + private boolean mReportLocation; public PassiveProvider(ILocationManager locationManager) { mLocationManager = locationManager; } + @Override public String getName() { return LocationManager.PASSIVE_PROVIDER; } - public boolean requiresNetwork() { - return false; - } - - public boolean requiresSatellite() { - return false; - } - - public boolean requiresCell() { - return false; - } - - public boolean hasMonetaryCost() { - return false; - } - - public boolean supportsAltitude() { - return false; - } - - public boolean supportsSpeed() { - return false; - } - - public boolean supportsBearing() { - return false; - } - - public int getPowerRequirement() { - return -1; - } - - public boolean meetsCriteria(Criteria criteria) { - // We do not want to match the special passive provider based on criteria. - return false; - } - - public int getAccuracy() { - return -1; + @Override + public ProviderProperties getProperties() { + return PROPERTIES; } + @Override public boolean isEnabled() { return true; } + @Override public void enable() { } + @Override public void disable() { } + @Override public int getStatus(Bundle extras) { - if (mTracking) { + if (mReportLocation) { return LocationProvider.AVAILABLE; } else { return LocationProvider.TEMPORARILY_UNAVAILABLE; } } + @Override public long getStatusUpdateTime() { return -1; } - public String getInternalState() { - return null; - } - - public void enableLocationTracking(boolean enable) { - mTracking = enable; - } - - public boolean requestSingleShotFix() { - return false; - } - - public void setMinTime(long minTime, WorkSource ws) { - } - - public void updateNetworkState(int state, NetworkInfo info) { + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + mReportLocation = request.reportLocation; } public void updateLocation(Location location) { - if (mTracking) { + if (mReportLocation) { try { // pass the location back to the location manager mLocationManager.reportLocation(location, true); @@ -141,13 +107,13 @@ public class PassiveProvider implements LocationProviderInterface { } } + @Override public boolean sendExtraCommand(String command, Bundle extras) { return false; } - public void addListener(int uid) { - } - - public void removeListener(int uid) { + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("mReportLocaiton=" + mReportLocation); } } |