Location overhaul, major commit.

Themes: Fused Location, Geofencing, LocationRequest.

API changes
o Fused location is always returned when asking for location by Criteria.
o Fused location is never returned as a LocationProvider object, nor returned
  as a provider String. This wouldn't make sense because the current API
  design assumes that LocationProvider's have fixed properties (accuracy, power
  etc).
o The fused location engine will tune itself based on the criteria passed
  by applications.
o Deprecate LocationProvider. Apps should use fused location (via Criteria
  class), instead of enumerating through LocationProvider objects. It is
  also over-engineered: designed for a world with a plethora of location
  providers that never materialized.
o The Criteria class is also over-engineered, with many methods that aren't
  currently used, but for now we won't deprecate them since they may have
  value in the future. It is now used to tune the fused location engine.
o Deprecate getBestProvider() and getProvider().
o Add getLastKnownLocation(Criteria), so we can return last known
  fused locations.
o Apps with only ACCESS_COARSE_LOCATION _can_ now use the GPS, but the location
  they receive will be fudged to a 1km radius. They can also use NETWORK
  and fused locatoins, which are fudged in the same way if necessary.
o Totally deprecate Criteria, in favor of LocationRequest.
  Criteria was designed to map QOS to a location provider. What we
  really need is to map QOS to _locations_.
  The death knell was the conflicting ACCURACY_ constants on
  Criteria, with values 1, 2, 3, 1, 2. Yes not a typo.
o Totally deprecate LocationProvider.
o Deprecate test/mock provider support. They require a named provider,
  which is a concept we are moving away from. We do not yet have a
  replacement, but I think its ok to deprecate since you also
  need to have 'allow mock locations' checked in developer settings.
  They will continue to work.
o Deprecate event codes associated with provider status. The fused
  provider is _always_ available.
o Introduce Geofence data object to provide an easier path fowards
  for polygons etc.

Implementation changes
o Fused implementation: incoming (GPS and NLP) location fixes are given
  a weight, that exponentially decays with respect to age and accuracy.
  The half-life of age is ~60 seconds, and the half-life of accuracy is
  ~20 meters. The fixes are weighted and combined to output a fused
  location.
o Move Fused Location impl into
  frameworks/base/packages/FusedLocation
o Refactor Fused Location behind the IProvider AIDL interface. This allow us
  to distribute newer versions of Fused Location in a new APK, at run-time.
o Introduce ServiceWatcher.java, to refactor code used for run-time upgrades of
  Fused Location, and the NLP.
o Fused Location is by default run in the system server (but can be moved to
  any process or pacakge, even at run-time).
o Plumb the Criteria requirements through to the Fused Location provider via
  ILocation.sendExtraCommand(). I re-used this interface to avoid modifying the
  ILocation interface, which would have broken run-time upgradability of the
  NLP.
o Switch the geofence manager to using fused location.
o Clean up 'adb shell dumpsys location' output.
o Introduce config_locationProviderPackageNames and
  config_overlay_locationProviderPackageNames to configure the default
  and overlay package names for Geocoder, NLP and FLP.
o Lots of misc cleanup.
o Improve location fudging. Apply random vector then quantize.
o Hide internal POJO's from clients of com.android.location.provider.jar
  (NLP and FLP). Introduce wrappers ProviderRequestUnbundled and
  ProviderPropertiesUnbundled.
o Introduce ProviderProperties to collapse all the provider accuracy/
  bearing/altitude/power plumbing (that is deprecated anyway).
o DELETE lots of code: DummyLocationProvider,
o Rename the (internal) LocationProvider to LocationProviderBase.
o Plumb pid, uid and packageName throughout
  LocationManagerService#Receiver to support future features.

TODO: The FLP and Geofencer have a lot of room to be more intelligent
TODO: Documentation
TODO: test test test

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