diff options
13 files changed, 969 insertions, 346 deletions
diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java index 9d258d0..6210a38 100644 --- a/location/java/android/location/Criteria.java +++ b/location/java/android/location/Criteria.java @@ -57,9 +57,81 @@ public class Criteria implements Parcelable { */ public static final int ACCURACY_COARSE = 2; - private int mAccuracy = NO_REQUIREMENT; + /** + * A constant indicating a low location accuracy requirement + * - may be used for horizontal, altitude, speed or bearing accuracy. + * For horizontal and vertical position this corresponds to an accuracy + * greater than 500 meters. For speed and bearing, this corresponds + * to greater than 5 meters/second velocity and 10 degrees for bearing. + * {@hide} + */ + public static final int ACCURACY_LOW = 1; + + /** + * A constant indicating a medium accuracy requirement + * - may be used for horizontal, altitude, speed or bearing accuracy. + * For horizontal position this corresponds to an accuracy of between + * 100 and 500 meters, and between 200 and 500 meters for vertical accuracy. + * For speed and bearing, this corresponds to 1 meter/second to 5 meters/second + * velocity and and between 5 and 10 degrees for bearing. + * {@hide} + */ + public static final int ACCURACY_MEDIUM = 2; + + /** + * a constant indicating a high accuracy requirement + * - may be used for horizontal, altitude, speed or bearing accuracy. + * For horizontal and vertical position this corresponds to an accuracy + * less than 100 meters. For speed and bearing, this corresponds + * to less 1 meter/second velocity less than 5 degrees for bearing. + * {@hide} + */ + public static final int ACCURACY_HIGH = 3; + + /** + * a constant indicating the best accuracy that is available for any + * location provider available + * - may be used for horizontal, altitude, speed or bearing accuracy. + * {@hide} + */ + public static final int ACCURACY_BEST = 4; + + /** + * A constant indicating horizontal accuracy has the top priority + * {@hide} + */ + public static final int HORIZONTAL_ACCURACY_PRIORITY = 1; + + /** + * A constant indicating altitude accuracy has the top priority + * {@hide} + */ + public static final int VERTICAL_ACCURACY_PRIORITY = 2; + + /** + * A constant indicating speed accuracy has the top priority + * {@hide} + */ + public static final int SPEED_ACCURACY_PRIORITY = 3; + + /** + * A constant indicating bearing accuracy has the top priority + * {@hide} + */ + public static final int BEARING_ACCURACY_PRIORITY = 4; + + /** + * A constant indicating power requirement has the top priority + * {@hide} + */ + public static final int POWER_REQUIREMENT_PRIORITY = 5; + + private int mHorizontalAccuracy = NO_REQUIREMENT; + private int mVerticalAccuracy = NO_REQUIREMENT; + private int mSpeedAccuracy = NO_REQUIREMENT; + private int mBearingAccuracy = NO_REQUIREMENT; + private int mPriority = HORIZONTAL_ACCURACY_PRIORITY; private int mPowerRequirement = NO_REQUIREMENT; -// private int mPreferredResponseTime = NO_REQUIREMENT; private boolean mAltitudeRequired = false; private boolean mBearingRequired = false; private boolean mSpeedRequired = false; @@ -77,9 +149,12 @@ public class Criteria implements Parcelable { * Constructs a new Criteria object that is a copy of the given criteria. */ public Criteria(Criteria criteria) { - mAccuracy = criteria.mAccuracy; + mHorizontalAccuracy = criteria.mHorizontalAccuracy; + mVerticalAccuracy = criteria.mVerticalAccuracy; + mSpeedAccuracy = criteria.mSpeedAccuracy; + mBearingAccuracy = criteria.mBearingAccuracy; + mPriority = criteria.mPriority; mPowerRequirement = criteria.mPowerRequirement; -// mPreferredResponseTime = criteria.mPreferredResponseTime; mAltitudeRequired = criteria.mAltitudeRequired; mBearingRequired = criteria.mBearingRequired; mSpeedRequired = criteria.mSpeedRequired; @@ -87,19 +162,159 @@ public class Criteria implements Parcelable { } /** + * Indicates the desired horizontal accuracy (latitude and longitude). + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH}, {@link #ACCURACY_BEST}, + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + * {@hide} + */ + public void setHorizontalAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_BEST) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mHorizontalAccuracy = accuracy; + } + + /** + * Returns a constant indicating the desired horizontal accuracy (latitude and longitude). + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH}, {@link #ACCURACY_BEST}, + * {@hide} + */ + public int getHorizontalAccuracy() { + return mHorizontalAccuracy; + } + + /** + * Indicates the desired vertical accuracy (altitude). + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH}, {@link #ACCURACY_BEST}, + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + * {@hide} + */ + public void setVerticalAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_BEST) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mVerticalAccuracy = accuracy; + } + + /** + * Returns a constant indicating the desired vertical accuracy (altitude). + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH}, {@link #ACCURACY_BEST}, + * {@hide} + */ + public int getVerticalAccuracy() { + return mVerticalAccuracy; + } + + /** + * Indicates the desired speed accuracy. + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH}, {@link #ACCURACY_BEST}, + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + * {@hide} + */ + public void setSpeedAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_BEST) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mSpeedAccuracy = accuracy; + } + + /** + * Returns a constant indicating the desired speed accuracy + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH}, {@link #ACCURACY_BEST}, + * {@hide} + */ + public int getSpeedAccuracy() { + return mSpeedAccuracy; + } + + /** + * Indicates the desired bearing accuracy. + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH}, {@link #ACCURACY_BEST}, + * More accurate location may consume more power and may take longer. + * + * @throws IllegalArgumentException if accuracy is not one of the supported constants + * {@hide} + */ + public void setBearingAccuracy(int accuracy) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_BEST) { + throw new IllegalArgumentException("accuracy=" + accuracy); + } + mBearingAccuracy = accuracy; + } + + /** + * Returns a constant indicating the desired bearing accuracy. + * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, + * {@link #ACCURACY_HIGH}, {@link #ACCURACY_BEST}, + * {@hide} + */ + public int getBearingAccuracy() { + return mBearingAccuracy; + } + + /** + * Indicates the top priority to optimize for if the criteria parameters are + * found to be in conflict. + * Since a location provider might only be able to optimize for one requirement, + * the other requirements are considered good to have, but not guaranteed. + * This parameter does not override the priorities communicated through the + * preferred accuracy and power consumption parameters. + * If this parameter is not specified and conflicts occur, the location manager + * will use thefollowing default priority (high priority to low priority): + * {@link #HORIZONTAL_ACCURACY_PRIORITY}, {@link #POWER_REQUIREMENT_PRIORITY}, + * {@link #VERTICAL_ACCURACY_PRIORITY}, {@link #SPEED_ACCURACY_PRIORITY}, + * {@link #BEARING_ACCURACY_PRIORITY}. + * {@hide} + */ + public void setPreferredPriority(int priority) { + if (priority < HORIZONTAL_ACCURACY_PRIORITY || priority > POWER_REQUIREMENT_PRIORITY) { + throw new IllegalArgumentException("priority=" + priority); + } + mPriority = priority; + } + + /** + * Returns a constant indicating the top priority to optimize for if the + * criteria parameters are found to be in conflict. + * The value can be {@link #HORIZONTAL_ACCURACY_PRIORITY}, + * {@link #VERTICAL_ACCURACY_PRIORITY}, {@link #SPEED_ACCURACY_PRIORITY}, + * {@link #BEARING_ACCURACY_PRIORITY} or {@link #POWER_REQUIREMENT_PRIORITY}. + * {@hide} + */ + public int getPriority() { + return mPriority; + } + + /** * Indicates the desired accuracy for latitude and longitude. Accuracy * may be {@link #ACCURACY_FINE} if desired location * is fine, else it can be {@link #ACCURACY_COARSE}. - * More accurate location usually consumes more power and may take - * longer. + * More accurate location may consume more power and may take longer. * - * @throws IllegalArgumentException if accuracy is negative + * @throws IllegalArgumentException if accuracy is not one of the supported constants */ public void setAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT && accuracy > ACCURACY_COARSE) { + if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) { throw new IllegalArgumentException("accuracy=" + accuracy); } - mAccuracy = accuracy; + if (accuracy == ACCURACY_FINE) { + mHorizontalAccuracy = ACCURACY_BEST; + } else { + mHorizontalAccuracy = ACCURACY_LOW; + } } /** @@ -108,7 +323,11 @@ public class Criteria implements Parcelable { * is fine, else it can be {@link #ACCURACY_COARSE}. */ public int getAccuracy() { - return mAccuracy; + if (mHorizontalAccuracy >= ACCURACY_HIGH) { + return ACCURACY_FINE; + } else { + return ACCURACY_COARSE; + } } /** @@ -131,20 +350,6 @@ public class Criteria implements Parcelable { return mPowerRequirement; } -// /** -// * Indicates the preferred response time of the provider, in milliseconds. -// */ -// public void setPreferredResponseTime(int time) { -// mPreferredResponseTime = time; -// } -// -// /** -// * Returns the preferred response time of the provider, in milliseconds. -// */ -// public int getPreferredResponseTime() { -// return mPreferredResponseTime; -// } - /** * Indicates whether the provider is allowed to incur monetary cost. */ @@ -211,9 +416,12 @@ public class Criteria implements Parcelable { new Parcelable.Creator<Criteria>() { public Criteria createFromParcel(Parcel in) { Criteria c = new Criteria(); - c.mAccuracy = in.readInt(); + c.mHorizontalAccuracy = in.readInt(); + c.mVerticalAccuracy = in.readInt(); + c.mSpeedAccuracy = in.readInt(); + c.mBearingAccuracy = in.readInt(); + c.mPriority = in.readInt(); c.mPowerRequirement = in.readInt(); -// c.mPreferredResponseTime = in.readInt(); c.mAltitudeRequired = in.readInt() != 0; c.mBearingRequired = in.readInt() != 0; c.mSpeedRequired = in.readInt() != 0; @@ -231,9 +439,12 @@ public class Criteria implements Parcelable { } public void writeToParcel(Parcel parcel, int flags) { - parcel.writeInt(mAccuracy); + parcel.writeInt(mHorizontalAccuracy); + parcel.writeInt(mVerticalAccuracy); + parcel.writeInt(mSpeedAccuracy); + parcel.writeInt(mBearingAccuracy); + parcel.writeInt(mPriority); parcel.writeInt(mPowerRequirement); -// parcel.writeInt(mPreferredResponseTime); parcel.writeInt(mAltitudeRequired ? 1 : 0); parcel.writeInt(mBearingRequired ? 1 : 0); parcel.writeInt(mSpeedRequired ? 1 : 0); diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 2c0399e..a86f329 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -18,6 +18,7 @@ package android.location; import android.app.PendingIntent; import android.location.Address; +import android.location.Criteria; import android.location.GeocoderParams; import android.location.IGeocodeProvider; import android.location.IGpsStatusListener; @@ -32,13 +33,15 @@ import android.os.Bundle; */ interface ILocationManager { - List getAllProviders(); - List getProviders(boolean enabledOnly); + 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(String provider, long minTime, float minDistance, - in ILocationListener listener); - void requestLocationUpdatesPI(String provider, long minTime, float minDistance, - in PendingIntent intent); + 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); diff --git a/location/java/android/location/ILocationProvider.aidl b/location/java/android/location/ILocationProvider.aidl index 97b283c..2b9782a 100644 --- a/location/java/android/location/ILocationProvider.aidl +++ b/location/java/android/location/ILocationProvider.aidl @@ -16,6 +16,7 @@ package android.location; +import android.location.Criteria; import android.location.Location; import android.net.NetworkInfo; import android.os.Bundle; @@ -34,6 +35,7 @@ interface ILocationProvider { boolean supportsSpeed(); boolean supportsBearing(); int getPowerRequirement(); + boolean meetsCriteria(in Criteria criteria); int getAccuracy(); void enable(); void disable(); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 28bc599..11beadc 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -28,8 +28,6 @@ import android.util.Log; import com.android.internal.location.DummyLocationProvider; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -249,15 +247,12 @@ public class LocationManager { * factory Context.getSystemService. */ public LocationManager(ILocationManager service) { - if (false) { - Log.d(TAG, "Constructor: service = " + service); - } mService = service; } private LocationProvider createProvider(String name, Bundle info) { DummyLocationProvider provider = - new DummyLocationProvider(name); + new DummyLocationProvider(name, mService); provider.setRequiresNetwork(info.getBoolean("network")); provider.setRequiresSatellite(info.getBoolean("satellite")); provider.setRequiresCell(info.getBoolean("cell")); @@ -299,7 +294,7 @@ public class LocationManager { */ public List<String> getProviders(boolean enabledOnly) { try { - return mService.getProviders(enabledOnly); + return mService.getProviders(null, enabledOnly); } catch (RemoteException ex) { Log.e(TAG, "getProviders: RemoteException", ex); } @@ -344,173 +339,15 @@ public class LocationManager { * @return list of Strings containing names of the providers */ public List<String> getProviders(Criteria criteria, boolean enabledOnly) { - List<String> goodProviders = Collections.emptyList(); - List<String> providers = getProviders(enabledOnly); - for (String providerName : providers) { - LocationProvider provider = getProvider(providerName); - if (provider != null && provider.meetsCriteria(criteria)) { - if (goodProviders.isEmpty()) { - goodProviders = new ArrayList<String>(); - } - goodProviders.add(providerName); - } - } - return goodProviders; - } - - /** - * Returns the next looser power requirement, in the sequence: - * - * POWER_LOW -> POWER_MEDIUM -> POWER_HIGH -> NO_REQUIREMENT - */ - private int nextPower(int power) { - switch (power) { - case Criteria.POWER_LOW: - return Criteria.POWER_MEDIUM; - case Criteria.POWER_MEDIUM: - return Criteria.POWER_HIGH; - case Criteria.POWER_HIGH: - return Criteria.NO_REQUIREMENT; - case Criteria.NO_REQUIREMENT: - default: - return Criteria.NO_REQUIREMENT; - } - } - - /** - * Returns the next looser accuracy requirement, in the sequence: - * - * ACCURACY_FINE -> ACCURACY_APPROXIMATE-> NO_REQUIREMENT - */ - private int nextAccuracy(int accuracy) { - if (accuracy == Criteria.ACCURACY_FINE) { - return Criteria.ACCURACY_COARSE; - } else { - return Criteria.NO_REQUIREMENT; - } - } - - private abstract class LpComparator implements Comparator<LocationProvider> { - - public int compare(int a1, int a2) { - if (a1 < a2) { - return -1; - } else if (a1 > a2) { - return 1; - } else { - return 0; - } - } - - public int compare(float a1, float a2) { - if (a1 < a2) { - return -1; - } else if (a1 > a2) { - return 1; - } else { - return 0; - } - } - } - - private class LpPowerComparator extends LpComparator { - public int compare(LocationProvider l1, LocationProvider l2) { - int a1 = l1.getPowerRequirement(); - int a2 = l2.getPowerRequirement(); - return compare(a1, a2); // Smaller is better - } - - public boolean equals(LocationProvider l1, LocationProvider l2) { - int a1 = l1.getPowerRequirement(); - int a2 = l2.getPowerRequirement(); - return a1 == a2; - } - } - - private class LpAccuracyComparator extends LpComparator { - public int compare(LocationProvider l1, LocationProvider l2) { - int a1 = l1.getAccuracy(); - int a2 = l2.getAccuracy(); - return compare(a1, a2); // Smaller is better - } - - public boolean equals(LocationProvider l1, LocationProvider l2) { - int a1 = l1.getAccuracy(); - int a2 = l2.getAccuracy(); - return a1 == a2; - } - } - - private class LpCapabilityComparator extends LpComparator { - - private static final int ALTITUDE_SCORE = 4; - private static final int BEARING_SCORE = 4; - private static final int SPEED_SCORE = 4; - - private int score(LocationProvider p) { - return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) + - (p.supportsBearing() ? BEARING_SCORE : 0) + - (p.supportsSpeed() ? SPEED_SCORE : 0); - } - - public int compare(LocationProvider l1, LocationProvider l2) { - int a1 = score(l1); - int a2 = score(l2); - return compare(-a1, -a2); // Bigger is better - } - - public boolean equals(LocationProvider l1, LocationProvider l2) { - int a1 = score(l1); - int a2 = score(l2); - return a1 == a2; - } - } - - private LocationProvider best(List<String> providerNames) { - List<LocationProvider> providers = new ArrayList<LocationProvider>(providerNames.size()); - for (String name : providerNames) { - providers.add(getProvider(name)); - } - - if (providers.size() < 2) { - return providers.get(0); - } - - // First, sort by power requirement - Collections.sort(providers, new LpPowerComparator()); - int power = providers.get(0).getPowerRequirement(); - if (power < providers.get(1).getPowerRequirement()) { - return providers.get(0); - } - - int idx, size; - - List<LocationProvider> tmp = new ArrayList<LocationProvider>(); - idx = 0; - size = providers.size(); - while ((idx < size) && (providers.get(idx).getPowerRequirement() == power)) { - tmp.add(providers.get(idx)); - idx++; + if (criteria == null) { + throw new IllegalArgumentException("criteria==null"); } - - // Next, sort by accuracy - Collections.sort(tmp, new LpAccuracyComparator()); - int acc = tmp.get(0).getAccuracy(); - if (acc < tmp.get(1).getAccuracy()) { - return tmp.get(0); - } - - List<LocationProvider> tmp2 = new ArrayList<LocationProvider>(); - idx = 0; - size = tmp.size(); - while ((idx < size) && (tmp.get(idx).getAccuracy() == acc)) { - tmp2.add(tmp.get(idx)); - idx++; + try { + return mService.getProviders(criteria, enabledOnly); + } catch (RemoteException ex) { + Log.e(TAG, "getProviders: RemoteException", ex); } - - // Finally, sort by capability "score" - Collections.sort(tmp2, new LpCapabilityComparator()); - return tmp2.get(0); + return null; } /** @@ -536,72 +373,14 @@ public class LocationManager { * @return name of the provider that best matches the requirements */ public String getBestProvider(Criteria criteria, boolean enabledOnly) { - List<String> goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Make a copy of the criteria that we can modify - criteria = new Criteria(criteria); - - // Loosen power requirement - int power = criteria.getPowerRequirement(); - while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) { - power = nextPower(power); - criteria.setPowerRequirement(power); - goodProviders = getProviders(criteria, enabledOnly); - } - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - -// // Loosen response time requirement -// int responseTime = criteria.getPreferredResponseTime(); -// while (goodProviders.isEmpty() && -// (responseTime != Criteria.NO_REQUIREMENT)) { -// responseTime += 1000; -// if (responseTime > 60000) { -// responseTime = Criteria.NO_REQUIREMENT; -// } -// criteria.setPreferredResponseTime(responseTime); -// goodProviders = getProviders(criteria); -// } -// if (!goodProviders.isEmpty()) { -// return best(goodProviders); -// } - - // Loosen accuracy requirement - int accuracy = criteria.getAccuracy(); - while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) { - accuracy = nextAccuracy(accuracy); - criteria.setAccuracy(accuracy); - goodProviders = getProviders(criteria, enabledOnly); - } - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); + if (criteria == null) { + throw new IllegalArgumentException("criteria==null"); } - - // Remove bearing requirement - criteria.setBearingRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Remove speed requirement - criteria.setSpeedRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Remove altitude requirement - criteria.setAltitudeRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); + try { + return mService.getBestProvider(criteria, enabledOnly); + } catch (RemoteException ex) { + Log.e(TAG, "getBestProvider: RemoteException", ex); } - return null; } @@ -658,7 +437,7 @@ public class LocationManager { if (listener == null) { throw new IllegalArgumentException("listener==null"); } - _requestLocationUpdates(provider, minTime, minDistance, listener, null); + _requestLocationUpdates(provider, null, minTime, minDistance, false, listener, null); } /** @@ -701,10 +480,10 @@ public class LocationManager { * each location update * @param looper a Looper object whose message queue will be used to * implement the callback mechanism. + * If looper is null then the callbacks will be called on the main thread. * * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if listener is null - * @throws IllegalArgumentException if looper is null * @throws SecurityException if no suitable permission is present for the provider. */ public void requestLocationUpdates(String provider, @@ -716,15 +495,72 @@ public class LocationManager { if (listener == null) { throw new IllegalArgumentException("listener==null"); } - if (looper == null) { - throw new IllegalArgumentException("looper==null"); + _requestLocationUpdates(provider, null, minTime, minDistance, false, listener, looper); + } + + /** + * Registers the current activity to be notified periodically based on + * the specified criteria. Periodically, the supplied LocationListener will + * be called with the current Location or with status updates. + * + * <p> It may take a while to receive the most recent location. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * <p> In case the provider is disabled by the user, updates will stop, + * and the {@link LocationListener#onProviderDisabled(String)} + * method will be called. As soon as the provider is enabled again, + * the {@link LocationListener#onProviderEnabled(String)} method will + * be called and location updates will start again. + * + * <p> The frequency of notification may be controlled using the + * minTime and minDistance parameters. If minTime is greater than 0, + * the LocationManager could potentially rest for minTime milliseconds + * between location updates to conserve power. If minDistance is greater than 0, + * a location will only be broadcasted if the device moves by minDistance meters. + * To obtain notifications as frequently as possible, set both parameters to 0. + * + * <p> Background services should be careful about setting a sufficiently high + * minTime so that the device doesn't consume too much power by keeping the + * GPS or wireless radios on all the time. In particular, values under 60000ms + * are not recommended. + * + * <p> The supplied Looper is used to implement the callback mechanism. + * + * @param minTime the minimum time interval for notifications, in + * milliseconds. This field is only used as a hint to conserve power, and actual + * time between location updates may be greater or lesser than this value. + * @param minDistance the minimum distance interval for notifications, + * in meters + * @param criteria contains parameters for the location manager to choose the + * appropriate provider and parameters to compute the location + * @param listener a {#link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called for + * each location update + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism. + * If looper is null then the callbacks will be called on the main thread. + * + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if listener is null + * @throws SecurityException if no suitable permission is present to access + * the location services. + * + * {@hide} + */ + 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(provider, minTime, minDistance, listener, looper); + _requestLocationUpdates(null, criteria, minTime, minDistance, false, listener, looper); } - private void _requestLocationUpdates(String provider, - long minTime, float minDistance, LocationListener listener, - Looper looper) { + private void _requestLocationUpdates(String provider, Criteria criteria, long minTime, + float minDistance, boolean singleShot, LocationListener listener, Looper looper) { if (minTime < 0L) { minTime = 0L; } @@ -739,7 +575,7 @@ public class LocationManager { transport = new ListenerTransport(listener, looper); } mListeners.put(listener, transport); - mService.requestLocationUpdates(provider, minTime, minDistance, transport); + mService.requestLocationUpdates(provider, criteria, minTime, minDistance, singleShot, transport); } } catch (RemoteException ex) { Log.e(TAG, "requestLocationUpdates: DeadObjectException", ex); @@ -785,7 +621,7 @@ public class LocationManager { * time between location updates may be greater or lesser than this value. * @param minDistance the minimum distance interval for notifications, * in meters - * @param intent a {#link PendingIntet} to be sent for each location update + * @param intent a {#link PendingIntent} to be sent for each location update * * @throws IllegalArgumentException if provider is null or doesn't exist * @throws IllegalArgumentException if intent is null @@ -799,11 +635,69 @@ public class LocationManager { if (intent == null) { throw new IllegalArgumentException("intent==null"); } - _requestLocationUpdates(provider, minTime, minDistance, intent); + _requestLocationUpdates(provider, null, minTime, minDistance, false, intent); } - private void _requestLocationUpdates(String provider, - long minTime, float minDistance, PendingIntent intent) { + /** + * Registers the current activity to be notified periodically based on + * the specified criteria. Periodically, the supplied PendingIntent will + * be broadcast with the current Location or with status updates. + * + * <p> Location updates are sent with a key of KEY_LOCATION_CHANGED and a Location value. + * + * <p> It may take a while to receive the most recent location. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * <p> The frequency of notification or new locations may be controlled using the + * minTime and minDistance parameters. If minTime is greater than 0, + * the LocationManager could potentially rest for minTime milliseconds + * between location updates to conserve power. If minDistance is greater than 0, + * a location will only be broadcast if the device moves by minDistance meters. + * To obtain notifications as frequently as possible, set both parameters to 0. + * + * <p> Background services should be careful about setting a sufficiently high + * minTime so that the device doesn't consume too much power by keeping the + * GPS or wireless radios on all the time. In particular, values under 60000ms + * are not recommended. + * + * <p> In case the provider is disabled by the user, updates will stop, + * and an intent will be sent with an extra with key KEY_PROVIDER_ENABLED and a boolean value + * of false. If the provider is re-enabled, an intent will be sent with an + * extra with key KEY_PROVIDER_ENABLED and a boolean value of true and location updates will + * start again. + * + * <p> If the provider's status changes, an intent will be sent with an extra with key + * KEY_STATUS_CHANGED and an integer value indicating the new status. Any extras associated + * with the status update will be sent as well. + * + * @param minTime the minimum time interval for notifications, in + * milliseconds. This field is only used as a hint to conserve power, and actual + * time between location updates may be greater or lesser than this value. + * @param minDistance the minimum distance interval for notifications, + * in meters + * @param criteria contains parameters for the location manager to choose the + * appropriate provider and parameters to compute the location + * @param intent a {#link PendingIntent} to be sent for each location update + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if no suitable permission is present for the provider. + * + * {@hide} + */ + 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; } @@ -812,13 +706,158 @@ public class LocationManager { } try { - mService.requestLocationUpdatesPI(provider, minTime, minDistance, intent); + mService.requestLocationUpdatesPI(provider, criteria, minTime, minDistance, singleShot, intent); } catch (RemoteException ex) { Log.e(TAG, "requestLocationUpdates: RemoteException", ex); } } /** + * Registers the current activity to be notified periodically by + * the named provider. Periodically, the supplied LocationListener will + * be called with the current Location or with status updates. + * + * <p> It may take a while to receive the most recent location. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * <p> In case the provider is disabled by the user, updates will stop, + * and the {@link LocationListener#onProviderDisabled(String)} + * method will be called. As soon as the provider is enabled again, + * the {@link LocationListener#onProviderEnabled(String)} method will + * be called and location updates will start again. + * + * <p> The supplied Looper is used to implement the callback mechanism. + * + * @param provider the name of the provider with which to register + * @param listener a {#link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called for + * each location update + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism. + * If looper is null then the callbacks will be called on the main thread. + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if listener is null + * @throws SecurityException if no suitable permission is present for the provider. + * + * {@hide} + */ + 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); + } + + /** + * Registers the current activity to be notified periodically based on + * the specified criteria. Periodically, the supplied LocationListener will + * be called with the current Location or with status updates. + * + * <p> It may take a while to receive the most recent location. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * <p> In case the provider is disabled by the user, updates will stop, + * and the {@link LocationListener#onProviderDisabled(String)} + * method will be called. As soon as the provider is enabled again, + * the {@link LocationListener#onProviderEnabled(String)} method will + * be called and location updates will start again. + * + * <p> The supplied Looper is used to implement the callback mechanism. + * + * @param criteria contains parameters for the location manager to choose the + * appropriate provider and parameters to compute the location + * @param listener a {#link LocationListener} whose + * {@link LocationListener#onLocationChanged} method will be called for + * each location update + * @param looper a Looper object whose message queue will be used to + * implement the callback mechanism. + * If looper is null then the callbacks will be called on the current thread. + * + * @throws IllegalArgumentException if criteria is null + * @throws IllegalArgumentException if listener is null + * @throws SecurityException if no suitable permission is present to access + * the location services. + * + * {@hide} + */ + 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); + } + + /** + * Registers the current activity to be notified periodically by + * the named provider. Periodically, the supplied PendingIntent will + * be broadcast with the current Location or with status updates. + * + * <p> Location updates are sent with a key of KEY_LOCATION_CHANGED and a Location value. + * + * <p> It may take a while to receive the most recent location. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * @param provider the name of the provider with which to register + * @param intent a {#link PendingIntent} to be sent for the location update + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if no suitable permission is present for the provider. + * + * {@hide} + */ + 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); + } + + /** + * Registers the current activity to be notified periodically based on + * the specified criteria. Periodically, the supplied PendingIntent will + * be broadcast with the current Location or with status updates. + * + * <p> Location updates are sent with a key of KEY_LOCATION_CHANGED and a Location value. + * + * <p> It may take a while to receive the most recent location. If + * an immediate location is required, applications may use the + * {@link #getLastKnownLocation(String)} method. + * + * @param criteria contains parameters for the location manager to choose the + * appropriate provider and parameters to compute the location + * @param intent a {#link PendingIntent} to be sent for the location update + * + * @throws IllegalArgumentException if provider is null or doesn't exist + * @throws IllegalArgumentException if intent is null + * @throws SecurityException if no suitable permission is present for the provider. + * + * {@hide} + */ + public void requestSingleUpdate(Criteria criteria, PendingIntent intent) { + if (criteria == null) { + throw new IllegalArgumentException("criteria==null"); + } + if (intent == null) { + throw new IllegalArgumentException("intent==null"); + } + _requestLocationUpdates(null, criteria, 0L, 0.0f, true, intent); + } + + /** * Removes any current registration for location updates of the current activity * with the given LocationListener. Following this call, updates will no longer * occur for this listener. diff --git a/location/java/android/location/LocationProvider.java b/location/java/android/location/LocationProvider.java index bb3e2a5..8c16580 100644 --- a/location/java/android/location/LocationProvider.java +++ b/location/java/android/location/LocationProvider.java @@ -16,6 +16,9 @@ package android.location; +import android.os.RemoteException; +import android.util.Log; + /** * An abstract superclass for location providers. A location provider * provides periodic reports on the geographical location of the @@ -36,7 +39,8 @@ public abstract class LocationProvider { // in the name of a LocationProvider. static final String BAD_CHARS_REGEX = "[^a-zA-Z0-9]"; - private String mName; + private final String mName; + private final ILocationManager mService; public static final int OUT_OF_SERVICE = 0; public static final int TEMPORARILY_UNAVAILABLE = 1; @@ -50,13 +54,13 @@ public abstract class LocationProvider { * * {@hide} */ - public LocationProvider(String name) { + public LocationProvider(String name, ILocationManager service) { if (name.matches(BAD_CHARS_REGEX)) { throw new IllegalArgumentException("name " + name + " contains an illegal character"); } - // Log.d(TAG, "Constructor: name = " + name); mName = name; + mService = service; } /** @@ -71,29 +75,12 @@ public abstract class LocationProvider { * false otherwise. */ public boolean meetsCriteria(Criteria criteria) { - // We do not want to match the special passive provider based on criteria. - if (LocationManager.PASSIVE_PROVIDER.equals(mName)) { - return false; - } - 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()) { + try { + return mService.providerMeetsCriteria(mName, criteria); + } catch (RemoteException e) { + Log.e(TAG, "meetsCriteria: RemoteException", e); return false; } - return true; } /** diff --git a/location/java/android/location/provider/LocationProvider.java b/location/java/android/location/provider/LocationProvider.java index 56cfb33..1b5675d 100644 --- a/location/java/android/location/provider/LocationProvider.java +++ b/location/java/android/location/provider/LocationProvider.java @@ -18,6 +18,7 @@ package 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; @@ -75,6 +76,10 @@ public abstract class LocationProvider { return LocationProvider.this.onGetPowerRequirement(); } + public boolean meetsCriteria(Criteria criteria) { + return LocationProvider.this.onMeetsCriteria(criteria); + } + public int getAccuracy() { return LocationProvider.this.onGetAccuracy(); } @@ -226,6 +231,12 @@ public abstract class LocationProvider { 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 diff --git a/location/java/com/android/internal/location/DummyLocationProvider.java b/location/java/com/android/internal/location/DummyLocationProvider.java index ff5e27b..e7b5143 100644 --- a/location/java/com/android/internal/location/DummyLocationProvider.java +++ b/location/java/com/android/internal/location/DummyLocationProvider.java @@ -16,6 +16,7 @@ package com.android.internal.location; +import android.location.ILocationManager; import android.location.LocationProvider; /** @@ -41,8 +42,8 @@ public class DummyLocationProvider extends LocationProvider { int mPowerRequirement; int mAccuracy; - public DummyLocationProvider(String name) { - super(name); + public DummyLocationProvider(String name, ILocationManager service) { + super(name, service); } public void setRequiresNetwork(boolean requiresNetwork) { diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 33b0e81..74249f3 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -30,6 +30,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.Cursor; import android.location.Address; +import android.location.Criteria; import android.location.GeocoderParams; import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; @@ -68,6 +69,8 @@ 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.HashMap; import java.util.HashSet; import java.util.List; @@ -610,10 +613,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return out; } - public List<String> getProviders(boolean enabledOnly) { + public List<String> getProviders(Criteria criteria, boolean enabledOnly) { try { synchronized (mLock) { - return _getProvidersLocked(enabledOnly); + return _getProvidersLocked(criteria, enabledOnly); } } catch (SecurityException se) { throw se; @@ -623,7 +626,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private List<String> _getProvidersLocked(boolean enabledOnly) { + private List<String> _getProvidersLocked(Criteria criteria, boolean enabledOnly) { if (LOCAL_LOGV) { Slog.v(TAG, "getProviders"); } @@ -635,12 +638,225 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (enabledOnly && !isAllowedBySettingsLocked(name)) { continue; } + if (criteria != null && !p.meetsCriteria(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> { + 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> { + 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); + } + + 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>(mProviders.size()); + for (int i = mProviders.size() - 1; i >= 0; i--) { + providers.add(mProviders.get(i)); + } + } + + 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); + } + + /** + * Returns the name of the provider that best meets the given criteria. Only providers + * that are permitted to be accessed by the calling activity will be + * returned. If several providers meet the criteria, the one with the best + * accuracy is returned. If no provider meets the criteria, + * the criteria are loosened in the following sequence: + * + * <ul> + * <li> power requirement + * <li> accuracy + * <li> bearing + * <li> speed + * <li> altitude + * </ul> + * + * <p> Note that the requirement on monetary cost is not removed + * in this process. + * + * @param criteria the criteria that need to be matched + * @param enabledOnly if true then only a provider that is currently enabled is returned + * @return name of the provider that best matches the requirements + */ + public String getBestProvider(Criteria criteria, boolean enabledOnly) { + List<String> goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Make a copy of the criteria that we can modify + criteria = new Criteria(criteria); + + // Loosen power requirement + int power = criteria.getPowerRequirement(); + while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) { + power = nextPower(power); + criteria.setPowerRequirement(power); + goodProviders = getProviders(criteria, enabledOnly); + } + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Loosen accuracy requirement + int accuracy = criteria.getAccuracy(); + while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) { + accuracy = nextAccuracy(accuracy); + criteria.setAccuracy(accuracy); + goodProviders = getProviders(criteria, enabledOnly); + } + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove bearing requirement + criteria.setBearingRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove speed requirement + criteria.setSpeedRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + // Remove altitude requirement + criteria.setAltitudeRequired(false); + goodProviders = getProviders(criteria, enabledOnly); + if (!goodProviders.isEmpty()) { + return best(goodProviders).getName(); + } + + return null; + } + + public boolean providerMeetsCriteria(String provider, Criteria criteria) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) { + throw new IllegalArgumentException("provider=" + provider); + } + return p.meetsCriteria(criteria); + } + private void updateProvidersLocked() { for (int i = mProviders.size() - 1; i >= 0; i--) { LocationProviderInterface p = mProviders.get(i); @@ -717,6 +933,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run final Receiver mReceiver; final long mMinTime; final float mMinDistance; + final boolean mSingleShot; final int mUid; Location mLastFixBroadcast; long mLastStatusBroadcast; @@ -724,12 +941,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run /** * Note: must be constructed with lock held. */ - UpdateRecord(String provider, long minTime, float minDistance, + UpdateRecord(String provider, long minTime, float minDistance, boolean singleShot, Receiver receiver, int uid) { mProvider = provider; mReceiver = receiver; mMinTime = minTime; mMinDistance = minDistance; + mSingleShot = singleShot; mUid = uid; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); @@ -764,6 +982,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run 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) { @@ -819,12 +1038,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return false; } - public void requestLocationUpdates(String provider, - long minTime, float minDistance, ILocationListener listener) { - + 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, getReceiver(listener)); + requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, + getReceiver(listener)); } } catch (SecurityException se) { throw se; @@ -835,11 +1063,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - public void requestLocationUpdatesPI(String provider, - long minTime, float minDistance, PendingIntent intent) { + public void requestLocationUpdatesPI(String provider, Criteria criteria, + long minTime, float minDistance, boolean singleShot, PendingIntent 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, getReceiver(intent)); + requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, + getReceiver(intent)); } } catch (SecurityException se) { throw se; @@ -850,8 +1088,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private void requestLocationUpdatesLocked(String provider, - long minTime, float minDistance, Receiver receiver) { + private void requestLocationUpdatesLocked(String provider, long minTime, float minDistance, + boolean singleShot, Receiver receiver) { if (LOCAL_LOGV) { Slog.v(TAG, "_requestLocationUpdates: listener = " + receiver); } @@ -868,7 +1106,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run boolean newUid = !providerHasListener(provider, callingUid, null); long identity = Binder.clearCallingIdentity(); try { - UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, receiver, callingUid); + UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, singleShot, + receiver, callingUid); UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, r); if (oldRecord != null) { oldRecord.disposeLocked(); @@ -882,7 +1121,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (isProviderEnabled) { long minTimeForProvider = getMinTimeLocked(provider); p.setMinTime(minTimeForProvider); - p.enableLocationTracking(true); + // 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); @@ -1288,7 +1531,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run for (int i = mProviders.size() - 1; i >= 0; i--) { LocationProviderInterface provider = mProviders.get(i); - requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityReceiver); + requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, + false, mProximityReceiver); } } } @@ -1486,6 +1730,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run for (int i=0; i<N; i++) { UpdateRecord r = records.get(i); Receiver receiver = r.mReceiver; + boolean receiverDead = false; Location lastLoc = r.mLastFixBroadcast; if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) { @@ -1497,10 +1742,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } if (!receiver.callLocationChangedLocked(location)) { Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver); - if (deadReceivers == null) { - deadReceivers = new ArrayList<Receiver>(); - } - deadReceivers.add(receiver); + receiverDead = true; } } @@ -1510,13 +1752,18 @@ public class LocationManagerService extends ILocationManager.Stub implements Run r.mLastStatusBroadcast = newStatusUpdateTime; if (!receiver.callStatusChangedLocked(provider, status, extras)) { + receiverDead = true; Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver); - if (deadReceivers == null) { - deadReceivers = new ArrayList<Receiver>(); - } - if (!deadReceivers.contains(receiver)) { - deadReceivers.add(receiver); - } + } + } + + // remove receiver if it is dead or we just processed a single shot request + if (receiverDead || r.mSingleShot) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<Receiver>(); + } + if (!deadReceivers.contains(receiver)) { + deadReceivers.add(receiver); } } } diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index 19c9018..6fe02c2 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -150,6 +150,7 @@ public class GpsLocationProvider implements LocationProviderInterface { 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; private static final String PROPERTIES_FILE = "/etc/gps.conf"; @@ -190,6 +191,9 @@ public class GpsLocationProvider implements LocationProviderInterface { // 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; @@ -318,7 +322,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (action.equals(ALARM_WAKEUP)) { if (DEBUG) Log.d(TAG, "ALARM_WAKEUP"); - startNavigating(); + startNavigating(false); } else if (action.equals(ALARM_TIMEOUT)) { if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); hibernate(); @@ -599,6 +603,14 @@ public class GpsLocationProvider implements LocationProviderInterface { } /** + * 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 @@ -707,6 +719,7 @@ public class GpsLocationProvider implements LocationProviderInterface { } 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); } @@ -716,7 +729,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (enable) { mTTFF = 0; mLastFixTime = 0; - startNavigating(); + startNavigating(false); } else { if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { mAlarmManager.cancel(mWakeupIntent); @@ -726,6 +739,25 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + 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) { if (DEBUG) Log.d(TAG, "setMinTime " + minTime); @@ -875,16 +907,20 @@ public class GpsLocationProvider implements LocationProviderInterface { return false; } - private void startNavigating() { + private void startNavigating(boolean singleShot) { if (!mStarted) { if (DEBUG) Log.d(TAG, "startNavigating"); mStarted = true; - if (hasCapability(GPS_CAPABILITY_MSB) && - Settings.Secure.getInt(mContext.getContentResolver(), + mSingleShot = singleShot; + mPositionMode = GPS_POSITION_MODE_STANDALONE; + + if (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { - mPositionMode = GPS_POSITION_MODE_MS_BASED; - } else { - mPositionMode = GPS_POSITION_MODE_STANDALONE; + if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) { + mPositionMode = GPS_POSITION_MODE_MS_ASSISTED; + } else if (hasCapability(GPS_CAPABILITY_MSA)) { + mPositionMode = GPS_POSITION_MODE_MS_BASED; + } } int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000); @@ -918,6 +954,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (DEBUG) Log.d(TAG, "stopNavigating"); if (mStarted) { mStarted = false; + mSingleShot = false; native_stop(); mTTFF = 0; mLastFixTime = 0; @@ -1008,6 +1045,9 @@ 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 @@ -1022,7 +1062,7 @@ public class GpsLocationProvider implements LocationProviderInterface { updateStatus(LocationProvider.AVAILABLE, mSvCount); } - if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval > 1000) { + if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted && mFixInterval > 1000) { if (DEBUG) Log.d(TAG, "got fix, hibernating"); hibernate(); } @@ -1369,6 +1409,9 @@ public class GpsLocationProvider implements LocationProviderInterface { case ENABLE_TRACKING: handleEnableLocationTracking(msg.arg1 == 1); break; + case REQUEST_SINGLE_SHOT: + handleRequestSingleShot(); + break; case UPDATE_NETWORK_STATE: handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj); break; diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java index a472143..084ab81 100644 --- a/services/java/com/android/server/location/LocationProviderInterface.java +++ b/services/java/com/android/server/location/LocationProviderInterface.java @@ -16,6 +16,7 @@ package com.android.server.location; +import android.location.Criteria; import android.location.Location; import android.net.NetworkInfo; import android.os.Bundle; @@ -35,6 +36,7 @@ public interface LocationProviderInterface { boolean supportsSpeed(); boolean supportsBearing(); int getPowerRequirement(); + boolean meetsCriteria(Criteria criteria); int getAccuracy(); boolean isEnabled(); void enable(); @@ -42,6 +44,8 @@ public interface LocationProviderInterface { 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); void updateNetworkState(int state, NetworkInfo info); diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java index 3e118f9..24d7737 100644 --- a/services/java/com/android/server/location/LocationProviderProxy.java +++ b/services/java/com/android/server/location/LocationProviderProxy.java @@ -20,6 +20,7 @@ import android.content.ComponentName; 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; @@ -97,7 +98,7 @@ public class LocationProviderProxy implements LocationProviderInterface { if (mCachedAttributes == null) { try { - mCachedAttributes = new DummyLocationProvider(mName); + mCachedAttributes = new DummyLocationProvider(mName, null); mCachedAttributes.setRequiresNetwork(provider.requiresNetwork()); mCachedAttributes.setRequiresSatellite(provider.requiresSatellite()); mCachedAttributes.setRequiresCell(provider.requiresCell()); @@ -199,6 +200,39 @@ public class LocationProviderProxy implements LocationProviderInterface { } } + public boolean meetsCriteria(Criteria criteria) { + ILocationProvider provider; + synchronized (mServiceConnection) { + provider = mProvider; + } + 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; + } + public int getAccuracy() { if (mCachedAttributes != null) { return mCachedAttributes.getAccuracy(); @@ -297,6 +331,10 @@ public class LocationProviderProxy implements LocationProviderInterface { } } + public boolean requestSingleShotFix() { + return false; + } + public long getMinTime() { return mMinTime; } diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java index e3f3346..01b34b7 100644 --- a/services/java/com/android/server/location/MockProvider.java +++ b/services/java/com/android/server/location/MockProvider.java @@ -16,6 +16,7 @@ package com.android.server.location; +import android.location.Criteria; import android.location.ILocationManager; import android.location.Location; import android.location.LocationProvider; @@ -138,6 +139,28 @@ public class MockProvider implements LocationProviderInterface { 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; @@ -174,6 +197,10 @@ public class MockProvider implements LocationProviderInterface { public void enableLocationTracking(boolean enable) { } + public boolean requestSingleShotFix() { + return false; + } + public void setMinTime(long minTime) { } diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java index 5ed1558..7fc93f8 100644 --- a/services/java/com/android/server/location/PassiveProvider.java +++ b/services/java/com/android/server/location/PassiveProvider.java @@ -16,6 +16,7 @@ package com.android.server.location; +import android.location.Criteria; import android.location.ILocationManager; import android.location.Location; import android.location.LocationManager; @@ -79,6 +80,11 @@ public class PassiveProvider implements LocationProviderInterface { 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; } @@ -113,6 +119,10 @@ public class PassiveProvider implements LocationProviderInterface { mTracking = enable; } + public boolean requestSingleShotFix() { + return false; + } + public void setMinTime(long minTime) { } |