Code drop from //branches/cupcake/...@124589
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 1bf60d4..831d1d2 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -57,9 +57,9 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.telephony.CellLocation;
 import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.Config;
 import android.util.Log;
@@ -145,24 +145,25 @@
     private boolean mCellWakeLockAcquired = false;
 
     /**
-     * Mapping from listener IBinder to local Listener wrappers.
+     * Mapping from listener IBinder/PendingIntent to local Listener wrappers.
      */
-    private final HashMap<IBinder,Listener> mListeners =
-        new HashMap<IBinder,Listener>();
+    private final HashMap<Object,Receiver> mListeners =
+        new HashMap<Object,Receiver>();
 
     /**
-     * Mapping from listener IBinder to a map from provider name to UpdateRecord.
+     * Mapping from listener IBinder/PendingIntent to a map from provider name to UpdateRecord.
      */
-    private final HashMap<IBinder,HashMap<String,UpdateRecord>> mLocationListeners =
-        new HashMap<IBinder,HashMap<String,UpdateRecord>>();
+    private final HashMap<Object,HashMap<String,UpdateRecord>> mLocationListeners =
+        new HashMap<Object,HashMap<String,UpdateRecord>>();
 
     /**
-     * Mapping from listener IBinder to a map from provider name to last broadcast location.
+     * Mapping from listener IBinder/PendingIntent to a map from provider name to last broadcast
+     * location.
      */
-    private final HashMap<IBinder,HashMap<String,Location>> mLastFixBroadcast =
-        new HashMap<IBinder,HashMap<String,Location>>();
-    private final HashMap<IBinder,HashMap<String,Long>> mLastStatusBroadcast =
-        new HashMap<IBinder,HashMap<String,Long>>();
+    private final HashMap<Object,HashMap<String,Location>> mLastFixBroadcast =
+        new HashMap<Object,HashMap<String,Location>>();
+    private final HashMap<Object,HashMap<String,Long>> mLastStatusBroadcast =
+        new HashMap<Object,HashMap<String,Long>>();
 
     /**
      * Mapping from provider name to all its UpdateRecords
@@ -178,7 +179,7 @@
         new HashMap<String,Location>();
 
     // Proximity listeners
-    private ProximityListener mProximityListener = null;
+    private Receiver mProximityListener = null;
     private HashMap<PendingIntent,ProximityAlert> mProximityAlerts =
         new HashMap<PendingIntent,ProximityAlert>();
     private HashSet<ProximityAlert> mProximitiesEntered =
@@ -195,7 +196,7 @@
 
     // Last known cell service state
     private TelephonyManager mTelephonyManager;
-    private ServiceState mServiceState = new ServiceState();
+    private int mSignalStrength = -1;
 
     // Location collector
     private LocationCollector mCollector;
@@ -206,11 +207,82 @@
     // Wifi Manager
     private WifiManager mWifiManager;
 
-    private final class Listener implements IBinder.DeathRecipient {
+    /**
+     * A wrapper class holding either an ILocationListener or a PendingIntent to receive
+     * location updates.
+     */
+    private final class Receiver implements IBinder.DeathRecipient {
         final ILocationListener mListener;
+        final PendingIntent mPendingIntent;
 
-        Listener(ILocationListener listener) {
+        Receiver(ILocationListener listener) {
             mListener = listener;
+            mPendingIntent = null;
+        }
+
+        Receiver(PendingIntent intent) {
+            mPendingIntent = intent;
+            mListener = null;
+        }
+
+        public Object getKey() {
+            if (mListener != null) {
+                return mListener.asBinder();
+            } else {
+                return mPendingIntent;
+            }
+        }
+
+        public boolean isListener() {
+            return mListener != null;
+        }
+
+        public boolean isPendingIntent() {
+            return mPendingIntent != null;
+        }
+
+        public ILocationListener getListener() {
+            if (mListener != null) {
+                return mListener;
+            }
+            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 void onStatusChanged(String provider, int status, Bundle extras)
+        throws RemoteException {
+            if (mListener != null) {
+                mListener.onStatusChanged(provider, status, extras);
+            } else {
+                Intent statusChanged = new Intent();
+                statusChanged.putExtras(extras);
+                statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
+                try {
+                    mPendingIntent.send(mContext, 0, statusChanged, null, null);
+                } catch (PendingIntent.CanceledException e) {
+                    _removeUpdates(this);
+                }
+            }
+        }
+
+        public void onLocationChanged(Location location) throws RemoteException {
+            if (mListener != null) {
+                mListener.onLocationChanged(location);
+            } else {
+                Intent locationChanged = new Intent();
+                locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED, location);
+                try {
+                    mPendingIntent.send(mContext, 0, locationChanged, null, null);
+                } catch (PendingIntent.CanceledException e) {
+                    _removeUpdates(this);
+                }
+            }
         }
 
         public void binderDied() {
@@ -218,7 +290,7 @@
                 Log.d(TAG, "Location listener died");
             }
             synchronized (mLocationListeners) {
-                _removeUpdates(mListener);
+                _removeUpdates(this);
             }
         }
     }
@@ -228,7 +300,7 @@
         String s = null;
         try {
             File f = new File(LocationManager.SYSTEM_DIR + "/location."
-                + provider);
+                    + provider);
             if (!f.exists()) {
                 return null;
             }
@@ -443,13 +515,22 @@
         // Create location collector
         mCollector = new LocationCollector(mMasfClient);
 
+        // Alarm manager, needs to be done before calling loadProviders() below
+        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+        // 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);
+
         // Load providers
         loadProviders();
 
         // Listen for Radio changes
         mTelephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
         mTelephonyManager.listen(mPhoneStateListener,
-                PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_CELL_LOCATION);
+                PhoneStateListener.LISTEN_CELL_LOCATION |
+                PhoneStateListener.LISTEN_SIGNAL_STRENGTH |
+                PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
 
         // Register for Network (Wifi or Mobile) updates
         NetworkStateBroadcastReceiver networkReceiver = new NetworkStateBroadcastReceiver();
@@ -460,9 +541,6 @@
         networkIntentFilter.addAction(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION);
         context.registerReceiver(networkReceiver, networkIntentFilter);
 
-        // Alarm manager
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
         // Register for power updates
         PowerStateBroadcastReceiver powerStateReceiver = new PowerStateBroadcastReceiver();
         IntentFilter intentFilter = new IntentFilter();
@@ -472,10 +550,6 @@
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
         context.registerReceiver(powerStateReceiver, intentFilter);
 
-        // Create a wake lock
-        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
-
         // Get the wifi manager
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
 
@@ -510,8 +584,8 @@
         }
         // Use system settings
         ContentResolver resolver = mContext.getContentResolver();
-        String allowedProviders = Settings.System.getString(resolver,
-                        Settings.System.LOCATION_PROVIDERS_ALLOWED);
+        String allowedProviders = Settings.Secure.getString(resolver,
+           Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
 
         return ((allowedProviders != null) && (allowedProviders.contains(provider)));
     }
@@ -638,16 +712,28 @@
         }
 
         synchronized (mRecordsByProvider) {
-
             HashSet<UpdateRecord> records = mRecordsByProvider.get(provider);
             if (records != null) {
                 for (UpdateRecord record : records) {
-                    // Sends a notification message to the listener
+                    // Sends a notification message to the receiver
                     try {
-                        if (enabled) {
-                            record.mListener.mListener.onProviderEnabled(provider);
+                        Receiver receiver = record.mReceiver;
+                        if (receiver.isListener()) {
+                            if (enabled) {
+                                receiver.getListener().onProviderEnabled(provider);
+                            } else {
+                                receiver.getListener().onProviderDisabled(provider);
+                            }
                         } else {
-                            record.mListener.mListener.onProviderDisabled(provider);
+                            PendingIntent intent = receiver.getPendingIntent();
+                            Intent providerIntent = new Intent();
+                            providerIntent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, enabled);
+                            try {
+                                receiver.getPendingIntent().send(mContext, 0,
+                                     providerIntent, null, null);
+                            } catch (PendingIntent.CanceledException e) {
+                                _removeUpdates(receiver);
+                            }
                         }
                     } catch (RemoteException e) {
                         // The death link will clean this up.
@@ -694,15 +780,15 @@
 
     private class UpdateRecord {
         String mProvider;
-        Listener mListener;
+        Receiver mReceiver;
         long mMinTime;
         float mMinDistance;
         String[] mPackages;
 
-        UpdateRecord(String provider, long minTime, float minDistance, Listener listener,
-            String[] packages) {
+        UpdateRecord(String provider, long minTime, float minDistance,
+            Receiver receiver, String[] packages) {
             mProvider = provider;
-            mListener = listener;
+            mReceiver = receiver;
             mMinTime = minTime;
             mMinDistance = minDistance;
             mPackages = packages;
@@ -740,7 +826,20 @@
         long minTime, float minDistance, ILocationListener listener) {
 
         try {
-            _requestLocationUpdates(provider, minTime, minDistance, listener);
+            _requestLocationUpdates(provider, minTime, minDistance,
+                new Receiver(listener));
+        } catch (SecurityException se) {
+            throw se;
+        } catch (Exception e) {
+            Log.e(TAG, "requestUpdates got exception:", e);
+        }
+    }
+
+    public void requestLocationUpdatesPI(String provider,
+            long minTime, float minDistance, PendingIntent intent) {
+        try {
+            _requestLocationUpdates(provider, minTime, minDistance,
+                    new Receiver(intent));
         } catch (SecurityException se) {
             throw se;
         } catch (Exception e) {
@@ -749,9 +848,10 @@
     }
 
     private void _requestLocationUpdates(String provider,
-        long minTime, float minDistance, ILocationListener listener) {
+            long minTime, float minDistance, Receiver receiver) {
+        Object key = receiver.getKey();
         if (Config.LOGD) {
-            Log.d(TAG, "_requestLocationUpdates: listener = " + listener.asBinder());
+            Log.d(TAG, "_requestLocationUpdates: listener = " + key);
         }
 
         LocationProviderImpl impl = LocationProviderImpl.getProvider(provider);
@@ -766,24 +866,23 @@
         // so wakelock calls will succeed
         long identity = Binder.clearCallingIdentity();
         try {
-            Listener myListener = new Listener(listener);
-            UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, myListener, packages);
-
+            UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, receiver, packages);
             synchronized (mLocationListeners) {
-                IBinder binder = listener.asBinder();
-                if (mListeners.get(binder) == null) {
+                if (mListeners.get(key) == null) {
                     try {
-                        binder.linkToDeath(myListener, 0);
-                        mListeners.put(binder, myListener);
+                        if (receiver.isListener()) {
+                            receiver.getListener().asBinder().linkToDeath(receiver, 0);
+                        }
+                        mListeners.put(key, receiver);
                     } catch (RemoteException e) {
                         return;
                     }
                 }
 
-                HashMap<String,UpdateRecord> records = mLocationListeners.get(binder);
+                HashMap<String,UpdateRecord> records = mLocationListeners.get(key);
                 if (records == null) {
                     records = new HashMap<String,UpdateRecord>();
-                    mLocationListeners.put(binder, records);
+                    mLocationListeners.put(key, records);
                 }
                 UpdateRecord oldRecord = records.put(provider, r);
                 if (oldRecord != null) {
@@ -809,9 +908,12 @@
                 } else {
                     try {
                         // Notify the listener that updates are currently disabled
-                        listener.onProviderDisabled(provider);
+                        if (receiver.isListener()) {
+                            receiver.getListener().onProviderDisabled(provider);
+                        }
                     } catch(RemoteException e) {
-                        Log.w(TAG, "RemoteException calling onProviderDisabled on " + listener);
+                        Log.w(TAG, "RemoteException calling onProviderDisabled on " +
+                                receiver.getListener());
                     }
                 }
             }
@@ -822,7 +924,7 @@
 
     public void removeUpdates(ILocationListener listener) {
         try {
-            _removeUpdates(listener);
+            _removeUpdates(new Receiver(listener));
         } catch (SecurityException se) {
             throw se;
         } catch (Exception e) {
@@ -830,24 +932,34 @@
         }
     }
 
-    private void _removeUpdates(ILocationListener listener) {
+    public void removeUpdatesPI(PendingIntent intent) {
+        try {
+            _removeUpdates(new Receiver(intent));
+        } catch (SecurityException se) {
+            throw se;
+        } catch (Exception e) {
+            Log.e(TAG, "removeUpdates got exception:", e);
+        }
+    }
+
+    private void _removeUpdates(Receiver receiver) {
+        Object key = receiver.getKey();
         if (Config.LOGD) {
-            Log.d(TAG, "_removeUpdates: listener = " + listener.asBinder());
+            Log.d(TAG, "_removeUpdates: listener = " + key);
         }
 
         // so wakelock calls will succeed
         long identity = Binder.clearCallingIdentity();
         try {
             synchronized (mLocationListeners) {
-                IBinder binder = listener.asBinder();
-                Listener myListener = mListeners.remove(binder);
-                if (myListener != null) {
-                    binder.unlinkToDeath(myListener, 0);
+                Receiver myReceiver = mListeners.remove(key);
+                if ((myReceiver != null) && (myReceiver.isListener())) {
+                    myReceiver.getListener().asBinder().unlinkToDeath(myReceiver, 0);
                 }
 
                 // Record which providers were associated with this listener
                 HashSet<String> providers = new HashSet<String>();
-                HashMap<String,UpdateRecord> oldRecords = mLocationListeners.get(binder);
+                HashMap<String,UpdateRecord> oldRecords = mLocationListeners.get(key);
                 if (oldRecords != null) {
                     // Call dispose() on the obsolete update records.
                     for (UpdateRecord record : oldRecords.values()) {
@@ -862,9 +974,9 @@
                     providers.addAll(oldRecords.keySet());
                 }
 
-                mLocationListeners.remove(binder);
-                mLastFixBroadcast.remove(binder);
-                mLastStatusBroadcast.remove(binder);
+                mLocationListeners.remove(key);
+                mLastFixBroadcast.remove(key);
+                mLastStatusBroadcast.remove(key);
 
                 // See if the providers associated with this listener have any
                 // other listeners; if one does, inform it of the new smallest minTime
@@ -894,7 +1006,7 @@
                     }
                 }
 
-                updateWakelockStatus(mScreenOn);                
+                updateWakelockStatus(mScreenOn);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -905,6 +1017,11 @@
         if (mGpsLocationProvider == null) {
             return false;
         }
+        if (mContext.checkCallingPermission(ACCESS_FINE_LOCATION) !=
+            PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires ACCESS_FINE_LOCATION permission");
+        }
+
         try {
             mGpsLocationProvider.addGpsStatusListener(listener);
         } catch (RemoteException e) {
@@ -996,7 +1113,6 @@
             ArrayList<PendingIntent> intentsToRemove = null;
 
             for (ProximityAlert alert : mProximityAlerts.values()) {
-
                 PendingIntent intent = alert.getIntent();
                 long expiration = alert.getExpiration();
 
@@ -1115,7 +1231,7 @@
         mProximityAlerts.put(intent, alert);
 
         if (mProximityListener == null) {
-            mProximityListener = new ProximityListener();
+            mProximityListener = new Receiver(new ProximityListener());
 
             LocationProvider provider = LocationProviderImpl.getProvider(
                 LocationManager.GPS_PROVIDER);
@@ -1345,15 +1461,15 @@
 
         // Broadcast location or status to all listeners
         for (UpdateRecord r : records) {
-            ILocationListener listener = r.mListener.mListener;
-            IBinder binder = listener.asBinder();
+            Receiver receiver = r.mReceiver;
+            Object key = receiver.getKey();
 
             // Broadcast location only if it is valid
             if (locationValid) {
-                HashMap<String,Location> map = mLastFixBroadcast.get(binder);
+                HashMap<String,Location> map = mLastFixBroadcast.get(key);
                 if (map == null) {
                     map = new HashMap<String,Location>();
-                    mLastFixBroadcast.put(binder, map);
+                    mLastFixBroadcast.put(key, map);
                 }
                 Location lastLoc = map.get(provider);
                 if ((lastLoc == null) || shouldBroadcast(loc, lastLoc, r)) {
@@ -1364,19 +1480,19 @@
                         lastLoc.set(loc);
                     }
                     try {
-                        listener.onLocationChanged(loc);
+                        receiver.onLocationChanged(loc);
                     } catch (RemoteException doe) {
-                        Log.w(TAG, "RemoteException calling onLocationChanged on " + listener);
-                        _removeUpdates(listener);
+                        Log.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
+                        _removeUpdates(receiver);
                     }
                 }
             }
 
             // Broadcast status message
-            HashMap<String,Long> statusMap = mLastStatusBroadcast.get(binder);
+            HashMap<String,Long> statusMap = mLastStatusBroadcast.get(key);
             if (statusMap == null) {
                 statusMap = new HashMap<String,Long>();
-                mLastStatusBroadcast.put(binder, statusMap);
+                mLastStatusBroadcast.put(key, statusMap);
             }
             long prevStatusUpdateTime =
                 (statusMap.get(provider) != null) ? statusMap.get(provider) : 0;
@@ -1386,10 +1502,10 @@
 
                 statusMap.put(provider, newStatusUpdateTime);
                 try {
-                    listener.onStatusChanged(provider, status, extras);
+                    receiver.onStatusChanged(provider, status, extras);
                 } catch (RemoteException doe) {
-                    Log.w(TAG, "RemoteException calling onStatusChanged on " + listener);
-                    _removeUpdates(listener);
+                    Log.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
+                    _removeUpdates(receiver);
                 }
             }
         }
@@ -1423,7 +1539,8 @@
                     }
 
                     if ((mWakeLockAcquireTime != 0) &&
-                        (SystemClock.elapsedRealtime() - mWakeLockAcquireTime > MAX_TIME_FOR_WAKE_LOCK)) {
+                        (SystemClock.elapsedRealtime() - mWakeLockAcquireTime
+                            > MAX_TIME_FOR_WAKE_LOCK)) {
 
                         removeMessages(MESSAGE_ACQUIRE_WAKE_LOCK);
                         removeMessages(MESSAGE_RELEASE_WAKE_LOCK);
@@ -1449,7 +1566,7 @@
                     acquireWakeLock();
                 } else if (msg.what == MESSAGE_RELEASE_WAKE_LOCK) {
                     log("LocationWorkerHandler: Release");
-                    
+
                     // Update wakelock status so the next alarm is set before releasing wakelock
                     updateWakelockStatus(mScreenOn);
                     releaseWakeLock();
@@ -1462,22 +1579,24 @@
     }
 
     PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+
+        private CellState mLastCellState = null;
         @Override
         public void onCellLocationChanged(CellLocation cellLocation) {
             try {
-                ServiceState serviceState = mServiceState;
+                int asu = mSignalStrength;
 
                 // Gets cell state
-                CellState cellState = new CellState(serviceState, cellLocation);
+                mLastCellState = new CellState(mTelephonyManager, cellLocation, asu);
 
                 // Notify collector
-                mCollector.updateCellState(cellState);
+                mCollector.updateCellState(mLastCellState);
 
                 // Updates providers
                 List<LocationProviderImpl> providers = LocationProviderImpl.getProviders();
                 for (LocationProviderImpl provider : providers) {
                     if (provider.requiresCell()) {
-                        provider.updateCellState(cellState);
+                        provider.updateCellState(mLastCellState);
                     }
                 }
             } catch (Exception e) {
@@ -1486,8 +1605,19 @@
         }
 
         @Override
-        public void onServiceStateChanged(ServiceState serviceState) {
-            mServiceState = serviceState;
+        public void onSignalStrengthChanged(int asu) {
+            mSignalStrength = asu;
+
+            if (mLastCellState != null) {
+                mLastCellState.updateSignalStrength(asu);
+            }
+        }
+
+        @Override
+        public void onDataConnectionStateChanged(int state) {
+            if (mLastCellState != null) {
+                mLastCellState.updateRadioType(mTelephonyManager);
+            }
         }
     };
 
@@ -1578,7 +1708,8 @@
 
             } else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION)) {
 
-                final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED, false);
+                final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED,
+                    false);
 
                 if (!enabled) {
                     // When GPS is disabled, we are OK to release wake-lock
@@ -1608,7 +1739,7 @@
             if (screenOn) {
                 startGps();
             } else if (mScreenOn && !screenOn) {
-                
+
                 // We just turned the screen off so stop navigating
                 stopGps();
             }
@@ -1641,13 +1772,23 @@
     }
 
     private void acquireWakeLock() {
+        try {
+            acquireWakeLockX();
+        } catch (Exception e) {
+            // This is to catch a runtime exception thrown when we try to release an
+            // already released lock.
+            Log.e(TAG, "exception in acquireWakeLock()", e);
+        }
+    }
+
+    private void acquireWakeLockX() {
         if (mWakeLock.isHeld()) {
             log("Must release wakelock before acquiring");
             mWakeLockAcquireTime = 0;
             mWakeLock.release();
         }
 
-        boolean networkActive = (mNetworkLocationProvider != null) 
+        boolean networkActive = (mNetworkLocationProvider != null)
                 && mNetworkLocationProvider.isLocationTracking();
         boolean gpsActive = (mGpsLocationProvider != null)
                 && mGpsLocationProvider.isLocationTracking();
@@ -1668,7 +1809,7 @@
 
         // Start the gps provider
         startGps();
-        
+
         // Acquire cell lock
         if (mCellWakeLockAcquired) {
             // Lock is already acquired
@@ -1700,7 +1841,7 @@
     }
 
     private void startGps() {
-        boolean gpsActive = (mGpsLocationProvider != null) 
+        boolean gpsActive = (mGpsLocationProvider != null)
                     && mGpsLocationProvider.isLocationTracking();
         if (gpsActive) {
             mGpsLocationProvider.startNavigating();
@@ -1708,7 +1849,7 @@
     }
 
     private void stopGps() {
-        boolean gpsActive = mGpsLocationProvider != null 
+        boolean gpsActive = mGpsLocationProvider != null
                     && mGpsLocationProvider.isLocationTracking();
         if (gpsActive) {
             mGpsLocationProvider.stopNavigating();
@@ -1716,6 +1857,16 @@
     }
 
     private void releaseWakeLock() {
+        try {
+            releaseWakeLockX();
+        } catch (Exception e) {
+            // This is to catch a runtime exception thrown when we try to release an
+            // already released lock.
+            Log.e(TAG, "exception in releaseWakeLock()", e);
+        }
+    }
+
+    private void releaseWakeLockX() {
         // Release wifi lock
         WifiManager.WifiLock wifiLock = getWifiWakelock();
         if (wifiLock != null) {
@@ -1726,22 +1877,22 @@
         }
 
         if (!mScreenOn) {
-            
+
             // Stop the gps
             stopGps();
         }
-        
+
         // Release cell lock
         if (mCellWakeLockAcquired) {
             mTelephonyManager.disableLocationUpdates();
             mCellWakeLockAcquired = false;
         }
-        
+
         // Notify NetworkLocationProvider
         if (mNetworkLocationProvider != null) {
             mNetworkLocationProvider.updateCellLockStatus(mCellWakeLockAcquired);
         }
-        
+
         // Release wake lock
         mWakeLockAcquireTime = 0;
         if (mWakeLock.isHeld()) {
@@ -1751,7 +1902,7 @@
             log("Can't release wakelock again!");
         }
     }
-    
+
     // Geocoder
 
     public String getFromLocation(double latitude, double longitude, int maxResults,
@@ -1903,14 +2054,29 @@
             return mSupportsSpeed;
         }
     }
+    
+    private void checkMockPermissions() {
+        boolean allowMocks = false;
+        try {
+            allowMocks = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ALLOW_MOCK_LOCATION) == 1;
+        } catch (SettingNotFoundException e) {
+            // Do nothing
+        }
+        if (!allowMocks) {
+            throw new SecurityException("Requires ACCESS_MOCK_LOCATION secure setting");
+        }
+
+        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
+            PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
+        }            
+    }
 
     public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite,
         boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
         boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) {
-        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }
+        checkMockPermissions();
 
         MockProvider provider = new MockProvider(name, requiresNetwork, requiresSatellite,
             requiresCell, hasMonetaryCost, supportsAltitude,
@@ -1923,10 +2089,7 @@
     }
 
     public void removeTestProvider(String provider) {
-        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }
+        checkMockPermissions();
         LocationProviderImpl p = LocationProviderImpl.getProvider(provider);
         if (p == null) {
             throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
@@ -1936,10 +2099,7 @@
     }
 
     public void setTestProviderLocation(String provider, Location loc) {
-        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }
+        checkMockPermissions();
         if (LocationProviderImpl.getProvider(provider) == null) {
             throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
         }
@@ -1947,10 +2107,7 @@
     }
 
     public void clearTestProviderLocation(String provider) {
-        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }
+        checkMockPermissions();
         if (LocationProviderImpl.getProvider(provider) == null) {
             throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
         }
@@ -1958,10 +2115,7 @@
     }
 
     public void setTestProviderEnabled(String provider, boolean enabled) {
-        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }
+        checkMockPermissions();
         if (LocationProviderImpl.getProvider(provider) == null) {
             throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
         }
@@ -1976,10 +2130,7 @@
     }
 
     public void clearTestProviderEnabled(String provider) {
-        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }
+        checkMockPermissions();
         if (LocationProviderImpl.getProvider(provider) == null) {
             throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
         }
@@ -1989,10 +2140,7 @@
     }
 
     public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) {
-        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }
+        checkMockPermissions();
         if (LocationProviderImpl.getProvider(provider) == null) {
             throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
         }
@@ -2002,10 +2150,7 @@
     }
 
     public void clearTestProviderStatus(String provider) {
-        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
-            PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
-        }
+        checkMockPermissions();
         if (LocationProviderImpl.getProvider(provider) == null) {
             throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
         }