New Location Manager APIs for Criteria based requests and single shot mode.

Use MS-Assisted mode for single shot GPS fixes if it is supported.

Add finer grained control over accuracy to the android.location.Criteria class
and location criteria logic from LocationManager to LocationManagerService

Change-Id: I156b1f6c6a45d255c87ff917cf3e9726a6d7a75b
Signed-off-by: Mike Lockwood <lockwood@android.com>
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 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 @@
      * 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 @@
     }
 
     /**
+     * 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 @@
      * 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 @@
         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 @@
         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 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 @@
 
 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 @@
  */
 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 @@
     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 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 @@
      * 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 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 @@
      * @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);
-            }
+        if (criteria == null) {
+            throw new IllegalArgumentException("criteria==null");
         }
-        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;
+        try {
+            return mService.getProviders(criteria, enabledOnly);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "getProviders: RemoteException", ex);
         }
-    }
-
-    /**
-     * Returns the next looser accuracy requirement, in the sequence:
-     *
-     * ACCURACY_FINE -> ACCURACY_APPROXIMATE-> NO_REQUIREMENT
-     */
-    private int nextAccuracy(int accuracy) {
-        if (accuracy == Criteria.ACCURACY_FINE) {
-            return Criteria.ACCURACY_COARSE;
-        } else {
-            return Criteria.NO_REQUIREMENT;
-        }
-    }
-
-    private abstract class LpComparator implements Comparator<LocationProvider> {
-
-        public int compare(int a1, int a2) {
-            if (a1 < a2) {
-                return -1;
-            } else if (a1 > a2) {
-                return 1;
-            } else {
-                return 0;
-            }
-        }
-
-        public int compare(float a1, float a2) {
-            if (a1 < a2) {
-                return -1;
-            } else if (a1 > a2) {
-                return 1;
-            } else {
-                return 0;
-            }
-        }
-    }
-
-    private class LpPowerComparator extends LpComparator {
-        public int compare(LocationProvider l1, LocationProvider l2) {
-            int a1 = l1.getPowerRequirement();
-            int a2 = l2.getPowerRequirement();
-            return compare(a1, a2); // Smaller is better
-         }
-
-         public boolean equals(LocationProvider l1, LocationProvider l2) {
-             int a1 = l1.getPowerRequirement();
-             int a2 = l2.getPowerRequirement();
-             return a1 == a2;
-         }
-    }
-
-    private class LpAccuracyComparator extends LpComparator {
-        public int compare(LocationProvider l1, LocationProvider l2) {
-            int a1 = l1.getAccuracy();
-            int a2 = l2.getAccuracy();
-            return compare(a1, a2); // Smaller is better
-         }
-
-         public boolean equals(LocationProvider l1, LocationProvider l2) {
-             int a1 = l1.getAccuracy();
-             int a2 = l2.getAccuracy();
-             return a1 == a2;
-         }
-    }
-
-    private class LpCapabilityComparator extends LpComparator {
-
-        private static final int ALTITUDE_SCORE = 4;
-        private static final int BEARING_SCORE = 4;
-        private static final int SPEED_SCORE = 4;
-
-        private int score(LocationProvider p) {
-            return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) +
-                (p.supportsBearing() ? BEARING_SCORE : 0) +
-                (p.supportsSpeed() ? SPEED_SCORE : 0);
-        }
-
-        public int compare(LocationProvider l1, LocationProvider l2) {
-            int a1 = score(l1);
-            int a2 = score(l2);
-            return compare(-a1, -a2); // Bigger is better
-         }
-
-         public boolean equals(LocationProvider l1, LocationProvider l2) {
-             int a1 = score(l1);
-             int a2 = score(l2);
-             return a1 == a2;
-         }
-    }
-
-    private LocationProvider best(List<String> providerNames) {
-        List<LocationProvider> providers = new ArrayList<LocationProvider>(providerNames.size());
-        for (String name : providerNames) {
-            providers.add(getProvider(name));
-        }
-
-        if (providers.size() < 2) {
-            return providers.get(0);
-        }
-
-        // First, sort by power requirement
-        Collections.sort(providers, new LpPowerComparator());
-        int power = providers.get(0).getPowerRequirement();
-        if (power < providers.get(1).getPowerRequirement()) {
-            return providers.get(0);
-        }
-
-        int idx, size;
-
-        List<LocationProvider> tmp = new ArrayList<LocationProvider>();
-        idx = 0;
-        size = providers.size();
-        while ((idx < size) && (providers.get(idx).getPowerRequirement() == power)) {
-            tmp.add(providers.get(idx));
-            idx++;
-        }
-
-        // Next, sort by accuracy
-        Collections.sort(tmp, new LpAccuracyComparator());
-        int acc = tmp.get(0).getAccuracy();
-        if (acc < tmp.get(1).getAccuracy()) {
-            return tmp.get(0);
-        }
-
-        List<LocationProvider> tmp2 = new ArrayList<LocationProvider>();
-        idx = 0;
-        size = tmp.size();
-        while ((idx < size) && (tmp.get(idx).getAccuracy() == acc)) {
-            tmp2.add(tmp.get(idx));
-            idx++;
-        }
-
-        // Finally, sort by capability "score"
-        Collections.sort(tmp2, new LpCapabilityComparator());
-        return tmp2.get(0);
+        return null;
     }
 
     /**
@@ -536,72 +373,14 @@
      * @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();
+        if (criteria == null) {
+            throw new IllegalArgumentException("criteria==null");
         }
-
-        // 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);
+        try {
+            return mService.getBestProvider(criteria, enabledOnly);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "getBestProvider: RemoteException", ex);
         }
-        if (!goodProviders.isEmpty()) {
-            return best(goodProviders).getName();
-        }
-
-//        // Loosen response time requirement
-//        int responseTime = criteria.getPreferredResponseTime();
-//        while (goodProviders.isEmpty() &&
-//            (responseTime != Criteria.NO_REQUIREMENT)) {
-//            responseTime += 1000;
-//            if (responseTime > 60000) {
-//                responseTime = Criteria.NO_REQUIREMENT;
-//            }
-//            criteria.setPreferredResponseTime(responseTime);
-//            goodProviders = getProviders(criteria);
-//        }
-//        if (!goodProviders.isEmpty()) {
-//            return best(goodProviders);
-//        }
-
-        // Loosen accuracy requirement
-        int accuracy = criteria.getAccuracy();
-        while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) {
-            accuracy = nextAccuracy(accuracy);
-            criteria.setAccuracy(accuracy);
-            goodProviders = getProviders(criteria, enabledOnly);
-        }
-        if (!goodProviders.isEmpty()) {
-            return best(goodProviders).getName();
-        }
-
-        // Remove bearing requirement
-        criteria.setBearingRequired(false);
-        goodProviders = getProviders(criteria, enabledOnly);
-        if (!goodProviders.isEmpty()) {
-            return best(goodProviders).getName();
-        }
-
-        // Remove speed requirement
-        criteria.setSpeedRequired(false);
-        goodProviders = getProviders(criteria, enabledOnly);
-        if (!goodProviders.isEmpty()) {
-            return best(goodProviders).getName();
-        }
-
-        // Remove altitude requirement
-        criteria.setAltitudeRequired(false);
-        goodProviders = getProviders(criteria, enabledOnly);
-        if (!goodProviders.isEmpty()) {
-            return best(goodProviders).getName();
-        }
-
         return null;
     }
 
@@ -658,7 +437,7 @@
         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 @@
      * 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 @@
         if (listener == null) {
             throw new IllegalArgumentException("listener==null");
         }
-        if (looper == null) {
-            throw new IllegalArgumentException("looper==null");
-        }
-        _requestLocationUpdates(provider, minTime, minDistance, listener, looper);
+        _requestLocationUpdates(provider, null, minTime, minDistance, false, listener, looper);
     }
 
-    private void _requestLocationUpdates(String provider,
-        long minTime, float minDistance, LocationListener listener,
-        Looper 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(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;
         }
@@ -739,7 +575,7 @@
                     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 @@
      * 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 @@
         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 @@
         }
 
         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 @@
     // 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 @@
      *
      * {@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 @@
      * 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)) {
+        try {
+            return mService.providerMeetsCriteria(mName, criteria);
+        } catch (RemoteException e) {
+            Log.e(TAG, "meetsCriteria: RemoteException", e);
             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()) {
-            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 @@
 
 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 @@
             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 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 @@
     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.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 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 @@
         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 @@
         }
     }
 
-    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 @@
                 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 @@
         final Receiver mReceiver;
         final long mMinTime;
         final float mMinDistance;
+        final boolean mSingleShot;
         final int mUid;
         Location mLastFixBroadcast;
         long mLastStatusBroadcast;
@@ -724,12 +941,13 @@
         /**
          * 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 @@
             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 @@
         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 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 @@
         }
     }
 
-    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 @@
         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 @@
             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 @@
 
             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 @@
         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 @@
                 }
                 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 @@
 
                 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 @@
     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 @@
     // 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 @@
 
             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 @@
     }
 
     /**
+     * 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 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 @@
         if (enable) {
             mTTFF = 0;
             mLastFixTime = 0;
-            startNavigating();
+            startNavigating(false);
         } else {
             if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) {
                 mAlarmManager.cancel(mWakeupIntent);
@@ -726,6 +739,25 @@
         }
     }
 
+    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 @@
         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 @@
         if (DEBUG) Log.d(TAG, "stopNavigating");
         if (mStarted) {
             mStarted = false;
+            mSingleShot = false;
             native_stop();
             mTTFF = 0;
             mLastFixTime = 0;
@@ -1008,6 +1045,9 @@
             }
         }
 
+        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 @@
             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 @@
                 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 @@
     boolean supportsSpeed();
     boolean supportsBearing();
     int getPowerRequirement();
+    boolean meetsCriteria(Criteria criteria);
     int getAccuracy();
     boolean isEnabled();
     void enable();
@@ -42,6 +44,8 @@
     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.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 @@
 
             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 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 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 e3f33469..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 @@
         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 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 @@
         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 @@
         mTracking = enable;
     }
 
+    public boolean requestSingleShotFix() {
+        return false;
+    }
+
     public void setMinTime(long minTime) {
     }