AI 144372: Cleanup Settings support for enabling and disabling location providers:
  LocationManagerService now listens for changes to settings,
  making LocationManager.updateProviders() unnecessary.
  Removed LocationManager.updateProviders()
  Added Settings.Secure.setLocationProviderEnabled(), which is a thread-safe way
  of enabling or disabling a single location provider.
  This is safer than reading, modifying and writing the LOCATION_PROVIDERS_ALLOWED directly.
  BUG=1729031

Automated import of CL 144372
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 993e4dc..d7d7c2e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2113,6 +2113,46 @@
          * @hide
          */
         public static final String TTY_MODE_ENABLED = "tty_mode_enabled";
+
+        /**
+         * Helper method for determining if a location provider is enabled.
+         * @param cr the content resolver to use
+         * @param provider the location provider to query
+         * @return true if the provider is enabled
+         *
+         * @hide
+         */
+        public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
+            String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
+            if (allowedProviders != null) {
+                return (allowedProviders.equals(provider) ||
+                        allowedProviders.contains("," + provider + ",") ||
+                        allowedProviders.startsWith(provider + ",") ||
+                        allowedProviders.endsWith("," + provider));
+            }
+            return false;           
+        }
+
+        /**
+         * Thread-safe method for enabling or disabling a single location provider.
+         * @param cr the content resolver to use
+         * @param provider the location provider to enable or disable
+         * @param enabled true if the provider should be enabled
+         *
+         * @hide
+         */
+        public static final void setLocationProviderEnabled(ContentResolver cr,
+                String provider, boolean enabled) {
+            // to ensure thread safety, we write the provider name with a '+' or '-'
+            // and let the SettingsProvider handle it rather than reading and modifying
+            // the list of enabled providers.
+            if (enabled) {
+                provider = "+" + provider;
+            } else {
+                provider = "-" + provider;
+            }
+            putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
+        }
     }
     
     /**
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 69c404a..d0f9877 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -33,8 +33,6 @@
     List getAllProviders();
     List getProviders(boolean enabledOnly);
 
-    void updateProviders();
-
     void requestLocationUpdates(String provider, long minTime, float minDistance,
         in ILocationListener listener);
     void requestLocationUpdatesPI(String provider, long minTime, float minDistance,
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 022ee25..0c7254e 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -313,20 +313,6 @@
     }
 
     /**
-     * Propagates the enabled/disabled state of the providers from the system
-     * settings to the providers themselves.
-     *
-     * {@hide}
-     */
-    public void updateProviders() {
-        try {
-            mService.updateProviders();
-        } catch (RemoteException ex) {
-            Log.e(TAG, "updateProviders: RemoteException", ex);
-        }
-    }
-
-    /**
      * Returns the next looser power requirement, in the sequence:
      *
      * POWER_LOW -> POWER_MEDIUM -> POWER_HIGH -> NO_REQUIREMENT
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 333a450..6f430c4 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -244,6 +244,72 @@
         return values.length;
     }
 
+    /*
+     * Used to parse changes to the value of Settings.Secure.LOCATION_PROVIDERS_ALLOWED.
+     * This setting contains a list of the currently enabled location providers.
+     * But helper functions in android.providers.Settings can enable or disable
+     * a single provider by using a "+" or "-" prefix before the provider name.
+     */
+    private boolean parseProviderList(Uri url, ContentValues initialValues) {
+        String value = initialValues.getAsString(Settings.Secure.VALUE);
+        String newProviders = null;
+        if (value != null && value.length() > 1) {
+            char prefix = value.charAt(0);
+            if (prefix == '+' || prefix == '-') {
+                // skip prefix
+                value = value.substring(1);
+
+                // read list of enabled providers into "providers"
+                String providers = "";
+                String[] columns = {Settings.Secure.VALUE};
+                String where = Settings.Secure.NAME + "=\'" + Settings.Secure.LOCATION_PROVIDERS_ALLOWED + "\'";
+                Cursor cursor = query(url, columns, where, null, null);
+                if (cursor != null && cursor.getCount() == 1) {
+                    try {
+                        cursor.moveToFirst();
+                        providers = cursor.getString(0);
+                    } finally {
+                        cursor.close();
+                    }
+                }
+
+                int index = providers.indexOf(value);
+                int end = index + value.length();
+                // check for commas to avoid matching on partial string
+                if (index > 0 && providers.charAt(index - 1) != ',') index = -1;
+                if (end < providers.length() && providers.charAt(end) != ',') index = -1;
+
+                if (prefix == '+' && index < 0) {
+                    // append the provider to the list if not present
+                    if (providers.length() == 0) {
+                        newProviders = value;
+                    } else {
+                        newProviders = providers + ',' + value;
+                    }
+                } else if (prefix == '-' && index >= 0) {
+                    // remove the provider from the list if present
+                    // remove leading and trailing commas
+                    if (index > 0) index--;
+                    if (end < providers.length()) end++;
+
+                    newProviders = providers.substring(0, index);
+                    if (end < providers.length()) {
+                        newProviders += providers.substring(end);
+                    }
+                } else {
+                    // nothing changed, so no need to update the database
+                    return false;
+                }
+
+                if (newProviders != null) {
+                    initialValues.put(Settings.Secure.VALUE, newProviders);
+                }
+            }
+        }
+        
+        return true;
+    }
+
     @Override
     public Uri insert(Uri url, ContentValues initialValues) {
         SqlArguments args = new SqlArguments(url);
@@ -252,6 +318,13 @@
         }
         checkWritePermissions(args);
 
+        // Special case LOCATION_PROVIDERS_ALLOWED.
+        // Support enabling/disabling a single provider (using "+" or "-" prefix)
+        String name = initialValues.getAsString(Settings.Secure.NAME);
+        if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+            if (!parseProviderList(url, initialValues)) return null;
+        }
+
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         final long rowId = db.insert(args.table, null, initialValues);
         if (rowId <= 0) return null;
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index edee5f8..f3187d7 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -28,17 +28,21 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
 import java.util.Set;
 import java.util.regex.Pattern;
 
 import android.app.AlarmManager;
 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.PackageManager;
+import android.database.Cursor;
 import android.location.Address;
 import android.location.IGpsStatusListener;
 import android.location.ILocationListener;
@@ -222,6 +226,9 @@
     private int mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE;
     private boolean mWifiEnabled = false;
 
+    // for Settings change notification
+    private ContentQueryMap mSettings;
+
     /**
      * A wrapper class holding either an ILocationListener or a PendingIntent to receive
      * location updates.
@@ -345,6 +352,14 @@
         }
     }
 
+    private final class SettingsObserver implements Observer {
+        public void update(Observable o, Object arg) {
+            synchronized (mLocationListeners) {
+                updateProvidersLocked();
+            }
+        }
+    }
+
     private Location readLastKnownLocationLocked(String provider) {
         Location location = null;
         String s = null;
@@ -593,6 +608,16 @@
         intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
         context.registerReceiver(powerStateReceiver, intentFilter);
 
+        // listen for settings changes
+        ContentResolver resolver = mContext.getContentResolver();
+        Cursor settingsCursor = resolver.query(Settings.Secure.CONTENT_URI, null,
+                "(" + Settings.System.NAME + "=?)",
+                new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED},
+                null);
+        mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mLocationHandler);
+        SettingsObserver settingsObserver = new SettingsObserver();
+        mSettings.addObserver(settingsObserver);
+
         // Get the wifi manager
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
 
@@ -773,12 +798,6 @@
         return out;
     }
 
-    public void updateProviders() {
-        synchronized (mLocationListeners) {
-            updateProvidersLocked();
-        }
-    }
-
     private void updateProvidersLocked() {
         for (LocationProviderImpl p : LocationProviderImpl.getProviders()) {
             boolean isEnabled = p.isEnabled();