Improve geofencing: throttle location updates with distance to fence.

Previously any geofence (proximity alert) would turn the GPS on at full rate.
Now, we modify the GPS interval with the distance to the nearest geofence.
A speed of 100m/s is assumed to calculate the next GPS update.

Also
o Major refactor of geofencing code, to make it easier to continue to improve.
o Discard proximity alerts when an app is removed.
o Misc cleanup of nearby code. There are other upcoming changes
  that make this a good time for some house-keeping.

TODO:
The new geofencing heuristics are much better than before, but still
relatively naive. The next steps could be:
- Improve boundary detection
- Improve update thottling for large geofences
- Consider velocity when throttling

Change-Id: Ie6e23d2cb2b931eba5d2a2fc759543bb96e2f7d0
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 5e21403..cc2f2d8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -361,10 +361,10 @@
                     return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                 }});
 
-        registerService(LOCATION_SERVICE, new StaticServiceFetcher() {
-                public Object createStaticService() {
+        registerService(LOCATION_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
                     IBinder b = ServiceManager.getService(LOCATION_SERVICE);
-                    return new LocationManager(ILocationManager.Stub.asInterface(b));
+                    return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
                 }});
 
         registerService(NETWORK_POLICY_SERVICE, new ServiceFetcher() {
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 2255bf2..47b7adf 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -54,7 +54,7 @@
     boolean sendExtraCommand(String provider, String command, inout Bundle extras);
 
     void addProximityAlert(double latitude, double longitude, float distance,
-        long expiration, in PendingIntent intent);
+        long expiration, in PendingIntent intent, in String packageName);
     void removeProximityAlert(in PendingIntent intent);
 
     Bundle getProviderInfo(String provider);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 1299574..ff74f41 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -17,6 +17,7 @@
 package android.location;
 
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Looper;
@@ -52,7 +53,9 @@
  */
 public class LocationManager {
     private static final String TAG = "LocationManager";
-    private ILocationManager mService;
+
+    private final Context mContext;
+    private final ILocationManager mService;
     private final HashMap<GpsStatus.Listener, GpsStatusListenerTransport> mGpsStatusListeners =
             new HashMap<GpsStatus.Listener, GpsStatusListenerTransport>();
     private final HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport> mNmeaListeners =
@@ -193,6 +196,7 @@
             }
         }
 
+        @Override
         public void onLocationChanged(Location location) {
             Message msg = Message.obtain();
             msg.what = TYPE_LOCATION_CHANGED;
@@ -200,6 +204,7 @@
             mListenerHandler.sendMessage(msg);
         }
 
+        @Override
         public void onStatusChanged(String provider, int status, Bundle extras) {
             Message msg = Message.obtain();
             msg.what = TYPE_STATUS_CHANGED;
@@ -213,6 +218,7 @@
             mListenerHandler.sendMessage(msg);
         }
 
+        @Override
         public void onProviderEnabled(String provider) {
             Message msg = Message.obtain();
             msg.what = TYPE_PROVIDER_ENABLED;
@@ -220,6 +226,7 @@
             mListenerHandler.sendMessage(msg);
         }
 
+        @Override
         public void onProviderDisabled(String provider) {
             Message msg = Message.obtain();
             msg.what = TYPE_PROVIDER_DISABLED;
@@ -260,8 +267,9 @@
      * right way to create an instance of this class is using the
      * factory Context.getSystemService.
      */
-    public LocationManager(ILocationManager service) {
+    public LocationManager(Context context, ILocationManager service) {
         mService = service;
+        mContext = context;
     }
 
     private LocationProvider createProvider(String name, Bundle info) {
@@ -1086,8 +1094,8 @@
                 ", intent = " + intent);
         }
         try {
-            mService.addProximityAlert(latitude, longitude, radius,
-                                       expiration, intent);
+            mService.addProximityAlert(latitude, longitude, radius, expiration, intent,
+                    mContext.getPackageName());
         } catch (RemoteException ex) {
             Log.e(TAG, "addProximityAlert: RemoteException", ex);
         }
@@ -1361,6 +1369,7 @@
             mNmeaBuffer = new ArrayList<Nmea>();
         }
 
+        @Override
         public void onGpsStarted() {
             if (mListener != null) {
                 Message msg = Message.obtain();
@@ -1369,6 +1378,7 @@
             }
         }
 
+        @Override
         public void onGpsStopped() {
             if (mListener != null) {
                 Message msg = Message.obtain();
@@ -1377,6 +1387,7 @@
             }
         }
 
+        @Override
         public void onFirstFix(int ttff) {
             if (mListener != null) {
                 mGpsStatus.setTimeToFirstFix(ttff);
@@ -1386,6 +1397,7 @@
             }
         }
 
+        @Override
         public void onSvStatusChanged(int svCount, int[] prns, float[] snrs,
                 float[] elevations, float[] azimuths, int ephemerisMask,
                 int almanacMask, int usedInFixMask) {
@@ -1401,6 +1413,7 @@
             }
         }
 
+        @Override
         public void onNmeaReceived(long timestamp, String nmea) {
             if (mNmeaListener != null) {
                 synchronized (mNmeaBuffer) {
diff --git a/location/java/com/android/internal/location/DummyLocationProvider.java b/location/java/com/android/internal/location/DummyLocationProvider.java
index e7b5143..3122960 100644
--- a/location/java/com/android/internal/location/DummyLocationProvider.java
+++ b/location/java/com/android/internal/location/DummyLocationProvider.java
@@ -24,7 +24,7 @@
  * 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 {
@@ -86,6 +86,7 @@
      * Returns true if the provider requires access to a
      * data network (e.g., the Internet), false otherwise.
      */
+    @Override
     public boolean requiresNetwork() {
         return mRequiresNetwork;
     }
@@ -95,6 +96,7 @@
      * satellite-based positioning system (e.g., GPS), false
      * otherwise.
      */
+    @Override
     public boolean requiresSatellite() {
         return mRequiresSatellite;
     }
@@ -104,6 +106,7 @@
      * cellular network (e.g., to make use of cell tower IDs), false
      * otherwise.
      */
+    @Override
     public boolean requiresCell() {
         return mRequiresCell;
     }
@@ -113,6 +116,7 @@
      * 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;
     }
@@ -123,6 +127,7 @@
      * under most circumstances but may occassionally not report it
      * should return true.
      */
+    @Override
     public boolean supportsAltitude() {
         return mSupportsAltitude;
     }
@@ -133,6 +138,7 @@
      * under most circumstances but may occassionally not report it
      * should return true.
      */
+    @Override
     public boolean supportsSpeed() {
         return mSupportsSpeed;
     }
@@ -143,6 +149,7 @@
      * under most circumstances but may occassionally not report it
      * should return true.
      */
+    @Override
     public boolean supportsBearing() {
         return mSupportsBearing;
     }
@@ -153,6 +160,7 @@
      * @return the power requirement for this provider, as one of the
      * constants Criteria.POWER_REQUIREMENT_*.
      */
+    @Override
     public int getPowerRequirement() {
         return mPowerRequirement;
     }
@@ -164,6 +172,7 @@
      * @return the horizontal accuracy for this provider, as one of the
      * constants Criteria.ACCURACY_*.
      */
+    @Override
     public int getAccuracy() {
         return mAccuracy;
     }
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 2918dbc..06b056d 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -19,18 +19,13 @@
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.ContentQueryMap;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.Signature;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.location.Address;
@@ -46,7 +41,6 @@
 import android.location.LocationProvider;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -58,14 +52,15 @@
 import android.os.RemoteException;
 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.GpsNetInitiatedHandler;
 
 import com.android.server.location.GeocoderProxy;
+import com.android.server.location.GeofenceManager;
 import com.android.server.location.GpsLocationProvider;
 import com.android.server.location.LocationProviderInterface;
 import com.android.server.location.LocationProviderProxy;
@@ -95,9 +90,6 @@
     private static final String TAG = "LocationManagerService";
     private static final boolean LOCAL_LOGV = false;
 
-    // The last time a location was written, by provider name.
-    private HashMap<String,Long> mLastWriteTime = new HashMap<String,Long>();
-
     private static final String ACCESS_FINE_LOCATION =
         android.Manifest.permission.ACCESS_FINE_LOCATION;
     private static final String ACCESS_COARSE_LOCATION =
@@ -147,7 +139,7 @@
     private final static String WAKELOCK_KEY = "LocationManagerService";
     private PowerManager.WakeLock mWakeLock = null;
     private int mPendingBroadcasts;
-    
+
     /**
      * List of all receivers.
      */
@@ -179,13 +171,7 @@
      */
     private final WorkSource mTmpWorkSource = new WorkSource();
 
-    // Proximity listeners
-    private Receiver mProximityReceiver = null;
-    private ILocationListener mProximityListener = null;
-    private HashMap<PendingIntent,ProximityAlert> mProximityAlerts =
-        new HashMap<PendingIntent,ProximityAlert>();
-    private HashSet<ProximityAlert> mProximitiesEntered =
-        new HashSet<ProximityAlert>();
+    GeofenceManager mGeofenceManager;
 
     // Last known location for each provider
     private HashMap<String,Location> mLastKnownLocation =
@@ -266,13 +252,6 @@
             throw new IllegalStateException("Request for non-existent listener");
         }
 
-        public PendingIntent getPendingIntent() {
-            if (mPendingIntent != null) {
-                return mPendingIntent;
-            }
-            throw new IllegalStateException("Request for non-existent intent");
-        }
-
         public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
             if (mListener != null) {
                 try {
@@ -280,11 +259,9 @@
                         // synchronize to ensure incrementPendingBroadcastsLocked()
                         // is called before decrementPendingBroadcasts()
                         mListener.onStatusChanged(provider, status, extras);
-                        if (mListener != mProximityListener) {
-                            // call this after broadcasting so we do not increment
-                            // if we throw an exeption.
-                            incrementPendingBroadcastsLocked();
-                        }
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
                     }
                 } catch (RemoteException e) {
                     return false;
@@ -317,11 +294,9 @@
                         // synchronize to ensure incrementPendingBroadcastsLocked()
                         // is called before decrementPendingBroadcasts()
                         mListener.onLocationChanged(location);
-                        if (mListener != mProximityListener) {
-                            // call this after broadcasting so we do not increment
-                            // if we throw an exeption.
-                            incrementPendingBroadcastsLocked();
-                        }
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
                     }
                 } catch (RemoteException e) {
                     return false;
@@ -357,11 +332,9 @@
                         } else {
                             mListener.onProviderDisabled(provider);
                         }
-                        if (mListener != mProximityListener) {
-                            // call this after broadcasting so we do not increment
-                            // if we throw an exeption.
-                            incrementPendingBroadcastsLocked();
-                        }
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
                     }
                 } catch (RemoteException e) {
                     return false;
@@ -402,6 +375,7 @@
             }
         }
 
+        @Override
         public void onSendFinished(PendingIntent pendingIntent, Intent intent,
                 int resultCode, String resultData, Bundle resultExtras) {
             synchronized (this) {
@@ -424,6 +398,7 @@
         }
     }
 
+    @Override
     public void locationCallbackFinished(ILocationListener listener) {
         //Do not use getReceiver here as that will add the ILocationListener to
         //the receiver list if it is not found.  If it is not found then the
@@ -442,6 +417,7 @@
     }
 
     private final class SettingsObserver implements Observer {
+        @Override
         public void update(Observable o, Object arg) {
             synchronized (mLock) {
                 updateProvidersLocked();
@@ -588,7 +564,7 @@
     }
 
     void systemReady() {
-        // we defer starting up the service until the system is ready 
+        // we defer starting up the service until the system is ready
         Thread thread = new Thread(null, this, "LocationManagerService");
         thread.start();
     }
@@ -616,20 +592,22 @@
         // listen for settings changes
         ContentResolver resolver = mContext.getContentResolver();
         Cursor settingsCursor = resolver.query(Settings.Secure.CONTENT_URI, null,
-                "(" + Settings.System.NAME + "=?)",
+                "(" + NameValueTable.NAME + "=?)",
                 new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED},
                 null);
-        mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mLocationHandler);
+        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();
     }
 
@@ -690,6 +668,7 @@
         return true;
     }
 
+    @Override
     public List<String> getAllProviders() {
         try {
             synchronized (mLock) {
@@ -715,6 +694,7 @@
         return out;
     }
 
+    @Override
     public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
         try {
             synchronized (mLock) {
@@ -782,6 +762,7 @@
     }
 
     private class LpPowerComparator implements Comparator<LocationProviderInterface> {
+        @Override
         public int compare(LocationProviderInterface l1, LocationProviderInterface l2) {
             // Smaller is better
             return (l1.getPowerRequirement() - l2.getPowerRequirement());
@@ -793,6 +774,7 @@
     }
 
     private class LpAccuracyComparator implements Comparator<LocationProviderInterface> {
+        @Override
         public int compare(LocationProviderInterface l1, LocationProviderInterface l2) {
             // Smaller is better
             return (l1.getAccuracy() - l2.getAccuracy());
@@ -815,6 +797,7 @@
                 (p.supportsSpeed() ? SPEED_SCORE : 0);
         }
 
+        @Override
         public int compare(LocationProviderInterface l1, LocationProviderInterface l2) {
             return (score(l2) - score(l1)); // Bigger is better
          }
@@ -896,6 +879,7 @@
      * @param enabledOnly if true then only a provider that is currently enabled is returned
      * @return name of the provider that best matches the requirements
      */
+    @Override
     public String getBestProvider(Criteria criteria, boolean enabledOnly) {
         List<String> goodProviders = getProviders(criteria, enabledOnly);
         if (!goodProviders.isEmpty()) {
@@ -951,6 +935,7 @@
         return null;
     }
 
+    @Override
     public boolean providerMeetsCriteria(String provider, Criteria criteria) {
         LocationProviderInterface p = mProvidersByName.get(provider);
         if (p == null) {
@@ -988,7 +973,7 @@
         }
 
         ArrayList<Receiver> deadReceivers = null;
-        
+
         ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
         if (records != null) {
             final int N = records.size();
@@ -1010,7 +995,7 @@
                 removeUpdatesLocked(deadReceivers.get(i));
             }
         }
-        
+
         if (enabled) {
             p.enable();
             if (listeners > 0) {
@@ -1148,14 +1133,10 @@
                 }
            }
         }
-        for (ProximityAlert alert : mProximityAlerts.values()) {
-            if (alert.mUid == uid) {
-                return true;
-            }
-        }
         return false;
     }
 
+    @Override
     public void requestLocationUpdates(String provider, Criteria criteria,
         long minTime, float minDistance, boolean singleShot, ILocationListener listener) {
         if (criteria != null) {
@@ -1181,6 +1162,20 @@
         }
     }
 
+    void validatePackageName(int uid, String packageName) {
+        if (packageName == null) {
+            throw new SecurityException("packageName cannot be null");
+        }
+        String[] packages = mPackageManager.getPackagesForUid(uid);
+        if (packages == null) {
+            throw new SecurityException("invalid UID " + uid);
+        }
+        for (String pkg : packages) {
+            if (packageName.equals(pkg)) return;
+        }
+        throw new SecurityException("invalid package name");
+    }
+
     void validatePendingIntent(PendingIntent intent) {
         if (intent.isTargetedToPackage()) {
             return;
@@ -1193,6 +1188,7 @@
         //        + intent);
     }
 
+    @Override
     public void requestLocationUpdatesPI(String provider, Criteria criteria,
             long minTime, float minDistance, boolean singleShot, PendingIntent intent) {
         validatePendingIntent(intent);
@@ -1270,6 +1266,7 @@
         }
     }
 
+    @Override
     public void removeUpdates(ILocationListener listener) {
         try {
             synchronized (mLock) {
@@ -1284,6 +1281,7 @@
         }
     }
 
+    @Override
     public void removeUpdatesPI(PendingIntent intent) {
         try {
             synchronized (mLock) {
@@ -1370,6 +1368,7 @@
         }
     }
 
+    @Override
     public boolean addGpsStatusListener(IGpsStatusListener listener) {
         if (mGpsStatusProvider == null) {
             return false;
@@ -1388,6 +1387,7 @@
         return true;
     }
 
+    @Override
     public void removeGpsStatusListener(IGpsStatusListener listener) {
         synchronized (mLock) {
             try {
@@ -1398,6 +1398,7 @@
         }
     }
 
+    @Override
     public boolean sendExtraCommand(String provider, String command, Bundle extras) {
         if (provider == null) {
             // throw NullPointerException to remain compatible with previous implementation
@@ -1417,11 +1418,12 @@
             if (p == null) {
                 return false;
             }
-    
+
             return p.sendExtraCommand(command, extras);
         }
     }
 
+    @Override
     public boolean sendNiResponse(int notifId, int userResponse)
     {
         if (Binder.getCallingUid() != Process.myUid()) {
@@ -1438,223 +1440,11 @@
         }
     }
 
-    class ProximityAlert {
-        final int  mUid;
-        final double mLatitude;
-        final double mLongitude;
-        final float mRadius;
-        final long mExpiration;
-        final PendingIntent mIntent;
-        final Location mLocation;
-
-        public ProximityAlert(int uid, double latitude, double longitude,
-            float radius, long expiration, PendingIntent intent) {
-            mUid = uid;
-            mLatitude = latitude;
-            mLongitude = longitude;
-            mRadius = radius;
-            mExpiration = expiration;
-            mIntent = intent;
-
-            mLocation = new Location("");
-            mLocation.setLatitude(latitude);
-            mLocation.setLongitude(longitude);
-        }
-
-        long getExpiration() {
-            return mExpiration;
-        }
-
-        PendingIntent getIntent() {
-            return mIntent;
-        }
-
-        boolean isInProximity(double latitude, double longitude, float accuracy) {
-            Location loc = new Location("");
-            loc.setLatitude(latitude);
-            loc.setLongitude(longitude);
-
-            double radius = loc.distanceTo(mLocation);
-            return radius <= Math.max(mRadius,accuracy);
-        }
-        
-        @Override
-        public String toString() {
-            return "ProximityAlert{"
-                    + Integer.toHexString(System.identityHashCode(this))
-                    + " uid " + mUid + mIntent + "}";
-        }
-        
-        void dump(PrintWriter pw, String prefix) {
-            pw.println(prefix + this);
-            pw.println(prefix + "mLatitude=" + mLatitude + " mLongitude=" + mLongitude);
-            pw.println(prefix + "mRadius=" + mRadius + " mExpiration=" + mExpiration);
-            pw.println(prefix + "mIntent=" + mIntent);
-            pw.println(prefix + "mLocation:");
-            mLocation.dump(new PrintWriterPrinter(pw), prefix + "  ");
-        }
-    }
-
-    // Listener for receiving locations to trigger proximity alerts
-    class ProximityListener extends ILocationListener.Stub implements PendingIntent.OnFinished {
-
-        boolean isGpsAvailable = false;
-
-        // Note: this is called with the lock held.
-        public void onLocationChanged(Location loc) {
-
-            // If Gps is available, then ignore updates from NetworkLocationProvider
-            if (loc.getProvider().equals(LocationManager.GPS_PROVIDER)) {
-                isGpsAvailable = true;
-            }
-            if (isGpsAvailable && loc.getProvider().equals(LocationManager.NETWORK_PROVIDER)) {
-                return;
-            }
-
-            // Process proximity alerts
-            long now = System.currentTimeMillis();
-            double latitude = loc.getLatitude();
-            double longitude = loc.getLongitude();
-            float accuracy = loc.getAccuracy();
-            ArrayList<PendingIntent> intentsToRemove = null;
-
-            for (ProximityAlert alert : mProximityAlerts.values()) {
-                PendingIntent intent = alert.getIntent();
-                long expiration = alert.getExpiration();
-
-                if ((expiration == -1) || (now <= expiration)) {
-                    boolean entered = mProximitiesEntered.contains(alert);
-                    boolean inProximity =
-                        alert.isInProximity(latitude, longitude, accuracy);
-                    if (!entered && inProximity) {
-                        if (LOCAL_LOGV) {
-                            Slog.v(TAG, "Entered alert");
-                        }
-                        mProximitiesEntered.add(alert);
-                        Intent enteredIntent = new Intent();
-                        enteredIntent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
-                        try {
-                            synchronized (this) {
-                                // synchronize to ensure incrementPendingBroadcasts()
-                                // is called before decrementPendingBroadcasts()
-                                intent.send(mContext, 0, enteredIntent, this, mLocationHandler,
-                                        ACCESS_FINE_LOCATION);
-                                // call this after broadcasting so we do not increment
-                                // if we throw an exeption.
-                                incrementPendingBroadcasts();
-                            }
-                        } catch (PendingIntent.CanceledException e) {
-                            if (LOCAL_LOGV) {
-                                Slog.v(TAG, "Canceled proximity alert: " + alert, e);
-                            }
-                            if (intentsToRemove == null) {
-                                intentsToRemove = new ArrayList<PendingIntent>();
-                            }
-                            intentsToRemove.add(intent);
-                        }
-                    } else if (entered && !inProximity) {
-                        if (LOCAL_LOGV) {
-                            Slog.v(TAG, "Exited alert");
-                        }
-                        mProximitiesEntered.remove(alert);
-                        Intent exitedIntent = new Intent();
-                        exitedIntent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
-                        try {
-                            synchronized (this) {
-                                // synchronize to ensure incrementPendingBroadcasts()
-                                // is called before decrementPendingBroadcasts()
-                                intent.send(mContext, 0, exitedIntent, this, mLocationHandler,
-                                        ACCESS_FINE_LOCATION);
-                                // call this after broadcasting so we do not increment
-                                // if we throw an exeption.
-                                incrementPendingBroadcasts();
-                            }
-                        } catch (PendingIntent.CanceledException e) {
-                            if (LOCAL_LOGV) {
-                                Slog.v(TAG, "Canceled proximity alert: " + alert, e);
-                            }
-                            if (intentsToRemove == null) {
-                                intentsToRemove = new ArrayList<PendingIntent>();
-                            }
-                            intentsToRemove.add(intent);
-                        }
-                    }
-                } else {
-                    // Mark alert for expiration
-                    if (LOCAL_LOGV) {
-                        Slog.v(TAG, "Expiring proximity alert: " + alert);
-                    }
-                    if (intentsToRemove == null) {
-                        intentsToRemove = new ArrayList<PendingIntent>();
-                    }
-                    intentsToRemove.add(alert.getIntent());
-                }
-            }
-
-            // Remove expired alerts
-            if (intentsToRemove != null) {
-                for (PendingIntent i : intentsToRemove) {
-                    ProximityAlert alert = mProximityAlerts.get(i);
-                    mProximitiesEntered.remove(alert);
-                    removeProximityAlertLocked(i);
-                }
-            }
-        }
-
-        // Note: this is called with the lock held.
-        public void onProviderDisabled(String provider) {
-            if (provider.equals(LocationManager.GPS_PROVIDER)) {
-                isGpsAvailable = false;
-            }
-        }
-
-        // Note: this is called with the lock held.
-        public void onProviderEnabled(String provider) {
-            // ignore
-        }
-
-        // Note: this is called with the lock held.
-        public void onStatusChanged(String provider, int status, Bundle extras) {
-            if ((provider.equals(LocationManager.GPS_PROVIDER)) &&
-                (status != LocationProvider.AVAILABLE)) {
-                isGpsAvailable = false;
-            }
-        }
-
-        public void onSendFinished(PendingIntent pendingIntent, Intent intent,
-                int resultCode, String resultData, Bundle resultExtras) {
-            // synchronize to ensure incrementPendingBroadcasts()
-            // is called before decrementPendingBroadcasts()
-            synchronized (this) {
-                decrementPendingBroadcasts();
-            }
-        }
-    }
-
+    @Override
     public void addProximityAlert(double latitude, double longitude,
-        float radius, long expiration, PendingIntent intent) {
+            float radius, long expiration, PendingIntent intent, String packageName) {
         validatePendingIntent(intent);
-        try {
-            synchronized (mLock) {
-                addProximityAlertLocked(latitude, longitude, radius, expiration, intent);
-            }
-        } catch (SecurityException se) {
-            throw se;
-        } catch (IllegalArgumentException iae) {
-            throw iae;
-        } catch (Exception e) {
-            Slog.e(TAG, "addProximityAlert got exception:", e);
-        }
-    }
-
-    private void addProximityAlertLocked(double latitude, double longitude,
-        float radius, long expiration, PendingIntent intent) {
-        if (LOCAL_LOGV) {
-            Slog.v(TAG, "addProximityAlert: latitude = " + latitude +
-                    ", longitude = " + longitude +
-                    ", expiration = " + expiration +
-                    ", intent = " + intent);
-        }
+        validatePackageName(Binder.getCallingUid(), packageName);
 
         // Require ability to access all providers for now
         if (!isAllowedProviderSafe(LocationManager.GPS_PROVIDER) ||
@@ -1662,59 +1452,28 @@
             throw new SecurityException("Requires ACCESS_FINE_LOCATION permission");
         }
 
-        if (expiration != -1) {
-            expiration += System.currentTimeMillis();
-        }
-        ProximityAlert alert = new ProximityAlert(Binder.getCallingUid(),
-                latitude, longitude, radius, expiration, intent);
-        mProximityAlerts.put(intent, alert);
+        if (LOCAL_LOGV) Slog.v(TAG, "addProximityAlert: lat=" + latitude + ", long=" + longitude +
+                ", radius=" + radius + ", exp=" + expiration + ", intent = " + intent);
 
-        if (mProximityReceiver == null) {
-            mProximityListener = new ProximityListener();
-            mProximityReceiver = new Receiver(mProximityListener);
-
-            for (int i = mProviders.size() - 1; i >= 0; i--) {
-                LocationProviderInterface provider = mProviders.get(i);
-                requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f,
-                        false, mProximityReceiver);
-            }
-        }
+        mGeofenceManager.addFence(latitude, longitude, radius, expiration, intent,
+                Binder.getCallingUid(), packageName);
     }
 
+    @Override
     public void removeProximityAlert(PendingIntent intent) {
-        try {
-            synchronized (mLock) {
-               removeProximityAlertLocked(intent);
-            }
-        } catch (SecurityException se) {
-            throw se;
-        } catch (IllegalArgumentException iae) {
-            throw iae;
-        } catch (Exception e) {
-            Slog.e(TAG, "removeProximityAlert got exception:", e);
-        }
+        if (intent == null) throw new NullPointerException("pending intent is null");
+
+        if (LOCAL_LOGV) Slog.v(TAG, "removeProximityAlert: intent = " + intent);
+
+        mGeofenceManager.removeFence(intent);
     }
 
-    private void removeProximityAlertLocked(PendingIntent intent) {
-        if (LOCAL_LOGV) {
-            Slog.v(TAG, "removeProximityAlert: intent = " + intent);
-        }
-
-        mProximityAlerts.remove(intent);
-        if (mProximityAlerts.size() == 0) {
-            if (mProximityReceiver != null) {
-                removeUpdatesLocked(mProximityReceiver);
-            }
-            mProximityReceiver = null;
-            mProximityListener = null;
-        }
-     }
-
     /**
      * @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) {
@@ -1752,6 +1511,7 @@
         return b;
     }
 
+    @Override
     public boolean isProviderEnabled(String provider) {
         try {
             synchronized (mLock) {
@@ -1765,6 +1525,7 @@
         }
     }
 
+    @Override
     public void reportLocation(Location location, boolean passive) {
         if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -1787,6 +1548,7 @@
         return isAllowedBySettingsLocked(provider);
     }
 
+    @Override
     public Location getLastKnownLocation(String provider) {
         if (LOCAL_LOGV) {
             Slog.v(TAG, "getLastKnownLocation: " + provider);
@@ -1869,7 +1631,7 @@
         int status = p.getStatus(extras);
 
         ArrayList<Receiver> deadReceivers = null;
-        
+
         // Broadcast location or status to all listeners
         final int N = records.size();
         for (int i=0; i<N; i++) {
@@ -1912,7 +1674,7 @@
                 }
             }
         }
-        
+
         if (deadReceivers != null) {
             for (int i=deadReceivers.size()-1; i>=0; i--) {
                 removeUpdatesLocked(deadReceivers.get(i));
@@ -2024,31 +1786,6 @@
                                     }
                                 }
                             }
-                            ArrayList<ProximityAlert> removedAlerts = null;
-                            for (ProximityAlert i : mProximityAlerts.values()) {
-                                if (i.mUid == uid) {
-                                    if (queryRestart) {
-                                        setResultCode(Activity.RESULT_OK);
-                                        return;
-                                    }
-                                    if (removedAlerts == null) {
-                                        removedAlerts = new ArrayList<ProximityAlert>();
-                                    }
-                                    if (!removedAlerts.contains(i)) {
-                                        removedAlerts.add(i);
-                                    }
-                                }
-                            }
-                            if (removedRecs != null) {
-                                for (int i=removedRecs.size()-1; i>=0; i--) {
-                                    removeUpdatesLocked(removedRecs.get(i));
-                                }
-                            }
-                            if (removedAlerts != null) {
-                                for (int i=removedAlerts.size()-1; i>=0; i--) {
-                                    removeProximityAlertLocked(removedAlerts.get(i).mIntent);
-                                }
-                            }
                         }
                     }
                 }
@@ -2089,6 +1826,10 @@
             // Called by main thread; divert work to LocationWorker.
             Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget();
         }
+        @Override
+        public void onPackageDisappeared(String packageName, int uid) {
+            mGeofenceManager.removeFence(packageName);
+        }
     };
 
     // Wake locks
@@ -2130,10 +1871,12 @@
 
     // Geocoder
 
+    @Override
     public boolean geocoderIsPresent() {
         return mGeocodeProvider != null;
     }
 
+    @Override
     public String getFromLocation(double latitude, double longitude, int maxResults,
             GeocoderParams params, List<Address> addrs) {
         if (mGeocodeProvider != null) {
@@ -2144,6 +1887,7 @@
     }
 
 
+    @Override
     public String getFromLocationName(String locationName,
             double lowerLeftLatitude, double lowerLeftLongitude,
             double upperRightLatitude, double upperRightLongitude, int maxResults,
@@ -2169,9 +1913,10 @@
         if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
             PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }            
+        }
     }
 
+    @Override
     public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite,
         boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
         boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) {
@@ -2207,6 +1952,7 @@
         Binder.restoreCallingIdentity(identity);
     }
 
+    @Override
     public void removeTestProvider(String provider) {
         checkMockPermissionsSafe();
         synchronized (mLock) {
@@ -2231,6 +1977,7 @@
         }
     }
 
+    @Override
     public void setTestProviderLocation(String provider, Location loc) {
         checkMockPermissionsSafe();
         synchronized (mLock) {
@@ -2245,6 +1992,7 @@
         }
     }
 
+    @Override
     public void clearTestProviderLocation(String provider) {
         checkMockPermissionsSafe();
         synchronized (mLock) {
@@ -2256,6 +2004,7 @@
         }
     }
 
+    @Override
     public void setTestProviderEnabled(String provider, boolean enabled) {
         checkMockPermissionsSafe();
         synchronized (mLock) {
@@ -2278,6 +2027,7 @@
         }
     }
 
+    @Override
     public void clearTestProviderEnabled(String provider) {
         checkMockPermissionsSafe();
         synchronized (mLock) {
@@ -2293,6 +2043,7 @@
         }
     }
 
+    @Override
     public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) {
         checkMockPermissionsSafe();
         synchronized (mLock) {
@@ -2304,6 +2055,7 @@
         }
     }
 
+    @Override
     public void clearTestProviderStatus(String provider) {
         checkMockPermissionsSafe();
         synchronized (mLock) {
@@ -2320,7 +2072,8 @@
             Slog.d(TAG, log);
         }
     }
-    
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -2329,7 +2082,7 @@
                     + ", uid=" + Binder.getCallingUid());
             return;
         }
-        
+
         synchronized (mLock) {
             pw.println("Current Location Manager state:");
             pw.println("  sProvidersLoaded=" + sProvidersLoaded);
@@ -2361,36 +2114,20 @@
                 pw.println("    " + i.getKey() + ":");
                 i.getValue().dump(new PrintWriterPrinter(pw), "      ");
             }
-            if (mProximityAlerts.size() > 0) {
-                pw.println("  Proximity Alerts:");
-                for (Map.Entry<PendingIntent, ProximityAlert> i
-                        : mProximityAlerts.entrySet()) {
-                    pw.println("    " + i.getKey() + ":");
-                    i.getValue().dump(pw, "      ");
-                }
-            }
-            if (mProximitiesEntered.size() > 0) {
-                pw.println("  Proximities Entered:");
-                for (ProximityAlert i : mProximitiesEntered) {
-                    pw.println("    " + i + ":");
-                    i.dump(pw, "      ");
-                }
-            }
-            pw.println("  mProximityReceiver=" + mProximityReceiver);
-            pw.println("  mProximityListener=" + mProximityListener);
+            mGeofenceManager.dump(pw);
             if (mEnabledProviders.size() > 0) {
                 pw.println("  Enabled Providers:");
                 for (String i : mEnabledProviders) {
                     pw.println("    " + i);
                 }
-                
+
             }
             if (mDisabledProviders.size() > 0) {
                 pw.println("  Disabled Providers:");
                 for (String i : mDisabledProviders) {
                     pw.println("    " + i);
                 }
-                
+
             }
             if (mMockProviders.size() > 0) {
                 pw.println("  Mock Providers:");
diff --git a/services/java/com/android/server/location/Geofence.java b/services/java/com/android/server/location/Geofence.java
new file mode 100644
index 0000000..f63607a
--- /dev/null
+++ b/services/java/com/android/server/location/Geofence.java
@@ -0,0 +1,100 @@
+/*
+ * 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.location;
+
+import android.location.Location;
+
+/**
+ * Represents a simple circular GeoFence.
+ */
+public class Geofence {
+    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;
+
+    final double mLatitude;
+    final double mLongitude;
+    final float mRadius;
+    final Location mLocation;
+
+    int mState;  // current state
+    double mDistance;  // current distance to center of fence
+
+    public Geofence(double latitude, double longitude, float radius, Location prevLocation) {
+        mState = STATE_UNKNOWN;
+
+        mLatitude = latitude;
+        mLongitude = longitude;
+        mRadius = radius;
+
+        mLocation = new Location("");
+        mLocation.setLatitude(latitude);
+        mLocation.setLongitude(longitude);
+
+        if (prevLocation != null) {
+            processLocation(prevLocation);
+        }
+    }
+
+    /**
+     * Process a new location.
+     * @return FLAG_ENTER or FLAG_EXIT if the fence was crossed, 0 otherwise
+     */
+    public int processLocation(Location location) {
+        mDistance = mLocation.distanceTo(location);
+
+        int prevState = mState;
+        //TODO: inside/outside detection could be made more rigorous
+        boolean inside = mDistance <= Math.max(mRadius, location.getAccuracy());
+        if (inside) {
+            mState = STATE_INSIDE;
+        } else {
+            mState = STATE_OUTSIDE;
+        }
+
+        if (prevState != 0 && mState != prevState) {
+            if (mState == STATE_INSIDE) return FLAG_ENTER;
+            if (mState == STATE_OUTSIDE) return FLAG_EXIT;
+        }
+        return 0;
+    }
+
+    public double getDistance() {
+        return mDistance;
+    }
+
+    @Override
+    public String toString() {
+        String state;
+        switch (mState) {
+            case STATE_INSIDE:
+                state = "IN";
+                break;
+            case STATE_OUTSIDE:
+                state = "OUT";
+                break;
+            default:
+                state = "?";
+        }
+        return String.format("(%.4f, %.4f r=%.0f d=%.0f %s)", mLatitude, mLongitude, mRadius,
+                mDistance, state);
+    }
+}
diff --git a/services/java/com/android/server/location/GeofenceManager.java b/services/java/com/android/server/location/GeofenceManager.java
new file mode 100644
index 0000000..b3f53ea
--- /dev/null
+++ b/services/java/com/android/server/location/GeofenceManager.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 20012 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.location;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import android.Manifest.permission;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+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";
+
+    /**
+     * Assume a maximum land speed, as a heuristic to throttle location updates.
+     * (Air travel should result in an airplane mode toggle which will
+     * force a new location update anyway).
+     */
+    static final int MAX_SPEED_M_S = 100;  // 360 km/hr (high speed train)
+
+    class GeofenceWrapper {
+        final Geofence fence;
+        final long expiry;
+        final String packageName;
+        final PendingIntent intent;
+
+        public GeofenceWrapper(Geofence fence, long expiry, String packageName,
+                PendingIntent intent) {
+            this.fence = fence;
+            this.expiry = expiry;
+            this.packageName = packageName;
+            this.intent = intent;
+        }
+    }
+
+    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>();
+
+    public GeofenceManager(Context context) {
+        mContext = context;
+        mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mLooper = Looper.myLooper();
+        mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
+    }
+
+    public void addFence(double latitude, double longitude, float radius, long expiration,
+            PendingIntent intent, int uid, String packageName) {
+        long expiry = SystemClock.elapsedRealtime() + expiration;
+        if (expiration < 0) {
+            expiry = Long.MAX_VALUE;
+        }
+        Geofence fence = new Geofence(latitude, longitude, radius, mLastLocation);
+        GeofenceWrapper fenceWrapper = new GeofenceWrapper(fence, expiry, packageName, intent);
+
+        synchronized (this) {
+            mFences.add(fenceWrapper);
+            updateProviderRequirements();
+        }
+    }
+
+    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();
+                }
+            }
+            updateProviderRequirements();
+        }
+    }
+
+    public void removeFence(String packageName) {
+        synchronized (this) {
+            Iterator<GeofenceWrapper> iter = mFences.iterator();
+            while (iter.hasNext()) {
+                GeofenceWrapper fenceWrapper = iter.next();
+                if (fenceWrapper.packageName.equals(packageName)) {
+                    iter.remove();
+                }
+            }
+            updateProviderRequirements();
+        }
+    }
+
+    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();
+                }
+            }
+        }
+    }
+
+    void processLocation(Location location) {
+        List<PendingIntent> enterIntents = new LinkedList<PendingIntent>();
+        List<PendingIntent> exitIntents = new LinkedList<PendingIntent>();
+
+        synchronized (this) {
+            mLastLocation = location;
+
+            removeExpiredFences();
+
+            for (GeofenceWrapper fenceWrapper : mFences) {
+                int event = fenceWrapper.fence.processLocation(location);
+                if ((event & Geofence.FLAG_ENTER) != 0) {
+                    enterIntents.add(fenceWrapper.intent);
+                }
+                if ((event & Geofence.FLAG_EXIT) != 0) {
+                    exitIntents.add(fenceWrapper.intent);
+                }
+            }
+            updateProviderRequirements();
+        }
+
+        // release lock before sending intents
+        for (PendingIntent intent : exitIntents) {
+            sendIntentExit(intent);
+        }
+        for (PendingIntent intent : enterIntents) {
+            sendIntentEnter(intent);
+        }
+    }
+
+    void sendIntentEnter(PendingIntent pendingIntent) {
+        Intent intent = new Intent();
+        intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
+        sendIntent(pendingIntent, intent);
+    }
+
+    void sendIntentExit(PendingIntent pendingIntent) {
+        Intent intent = new Intent();
+        intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
+        sendIntent(pendingIntent, intent);
+    }
+
+    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);
+            mWakeLock.release();
+        }
+    }
+
+    void updateProviderRequirements() {
+        synchronized (this) {
+            double minDistance = Double.MAX_VALUE;
+            for (GeofenceWrapper alert : mFences) {
+                if (alert.fence.getDistance() < minDistance) {
+                    minDistance = alert.fence.getDistance();
+                }
+            }
+
+            if (minDistance == Double.MAX_VALUE) {
+                disableLocation();
+            } else {
+                int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S;
+                setLocationInterval(intervalMs);
+            }
+        }
+    }
+
+    void setLocationInterval(int intervalMs) {
+        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, intervalMs, 0, this,
+                mLooper);
+    }
+
+    void disableLocation() {
+        mLocationManager.removeUpdates(this);
+    }
+
+    @Override
+    public void onLocationChanged(Location location) {
+        processLocation(location);
+    }
+
+    @Override
+    public void onStatusChanged(String provider, int status, Bundle extras) { }
+
+    @Override
+    public void onProviderEnabled(String provider) { }
+
+    @Override
+    public void onProviderDisabled(String provider) { }
+
+    @Override
+    public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+            String resultData, Bundle resultExtras) {
+        mWakeLock.release();
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("  Geofences:");
+        for (GeofenceWrapper fenceWrapper : mFences) {
+            pw.append("    ");
+            pw.append(fenceWrapper.packageName);
+            pw.append(" ");
+            pw.append(fenceWrapper.fence.toString());
+            pw.append("\n");
+        }
+    }
+}