Merge "Fixes for pin-restricted settings." into klp-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5fb7fe2..1cc6bfc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4659,7 +4659,7 @@
     <string name="global_font_change_title">Change font size</string>
 
     <!-- NFC payment settings --><skip/>
-    <string name="nfc_payment_settings_title">Tap and Pay</string>
+    <string name="nfc_payment_settings_title">Payments</string>
     <!-- Option to tell Android to ask the user which payment app to use every time
          a payment terminal is tapped -->
     <string name="nfc_payment_ask">Ask every time</string>
diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java
index 81c1794..ee4f018 100644
--- a/src/com/android/settings/WirelessSettings.java
+++ b/src/com/android/settings/WirelessSettings.java
@@ -28,7 +28,6 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.net.Uri;
 import android.nfc.NfcAdapter;
 import android.os.Bundle;
 import android.os.SystemProperties;
@@ -104,21 +103,22 @@
     }
 
     private String mManageMobilePlanMessage;
-
+    private static final String MOBILE_PROVISIONING_ACTION
+            = "com.android.server.connectivityservice.MOBILE_PROVISIONING_ACTION";
     public void onManageMobilePlanClick() {
         log("onManageMobilePlanClick:");
         mManageMobilePlanMessage = null;
         Resources resources = getActivity().getResources();
 
-        NetworkInfo ni = mCm.getActiveNetworkInfo();
+        NetworkInfo ni = mCm.getProvisioningOrActiveNetworkInfo();
         if (mTm.hasIccCard() && (ni != null)) {
             // Get provisioning URL
             String url = mCm.getMobileProvisioningUrl();
             if (!TextUtils.isEmpty(url)) {
-                // Send user to provisioning webpage
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                intent.setData(Uri.parse(url));
-                startActivity(intent);
+                Intent intent = new Intent(MOBILE_PROVISIONING_ACTION);
+                intent.putExtra("EXTRA_URL", url);
+                Context context = getActivity().getBaseContext();
+                context.sendBroadcast(intent);
                 mManageMobilePlanMessage = null;
             } else {
                 // No provisioning URL
diff --git a/src/com/android/settings/location/LocationSettingsBase.java b/src/com/android/settings/location/LocationSettingsBase.java
index 630e1e4..81e841a 100644
--- a/src/com/android/settings/location/LocationSettingsBase.java
+++ b/src/com/android/settings/location/LocationSettingsBase.java
@@ -16,58 +16,32 @@
 
 package com.android.settings.location;
 
-import android.content.ContentQueryMap;
+import android.app.LoaderManager.LoaderCallbacks;
 import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
 import android.database.Cursor;
+import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.Log;
 
 import com.android.settings.SettingsPreferenceFragment;
 
-import java.util.Observable;
-import java.util.Observer;
-
 /**
  * A base class that listens to location settings change and modifies location
  * settings.
  */
-public abstract class LocationSettingsBase extends SettingsPreferenceFragment {
-    private ContentQueryMap mContentQueryMap;
-    private Observer mSettingsObserver;
+public abstract class LocationSettingsBase extends SettingsPreferenceFragment
+        implements LoaderCallbacks<Cursor> {
+    private static final String TAG = "LocationSettingsBase";
+
+    private static final int LOADER_ID_LOCATION_MODE = 1;
 
     @Override
-    public void onStart() {
-        super.onStart();
-        // listen for Location Manager settings changes
-        Cursor settingsCursor = getContentResolver().query(Settings.Secure.CONTENT_URI, null,
-                "(" + Settings.System.NAME + "=?)",
-                new String[] { Settings.Secure.LOCATION_PROVIDERS_ALLOWED },
-                null);
-        mContentQueryMap = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, null);
-        mSettingsObserver = new Observer() {
-            @Override
-            public void update(Observable o, Object arg) {
-                refreshLocationMode();
-            }
-        };
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mContentQueryMap.addObserver(mSettingsObserver);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        mContentQueryMap.deleteObserver(mSettingsObserver);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        mContentQueryMap.close();
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        getLoaderManager().initLoader(LOADER_ID_LOCATION_MODE, null, this);
     }
 
     /** Called when location mode has changed. */
@@ -82,6 +56,9 @@
         if (isRestricted()) {
             // Location toggling disabled by user restriction. Read the current location mode to
             // update the location master switch.
+            if (Log.isLoggable(TAG, Log.INFO)) {
+                Log.i(TAG, "Restricted user, not setting location mode");
+            }
             mode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE,
                     Settings.Secure.LOCATION_MODE_OFF);
             onModeChanged(mode, true);
@@ -96,4 +73,26 @@
                 Settings.Secure.LOCATION_MODE_OFF);
         onModeChanged(mode, isRestricted());
     }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        switch (id) {
+            case LOADER_ID_LOCATION_MODE:
+                return new CursorLoader(getActivity(), Settings.Secure.CONTENT_URI, null,
+                        "(" + Settings.System.NAME + "=?)",
+                        new String[] { Settings.Secure.LOCATION_MODE }, null);
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        refreshLocationMode();
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+        // Nothing to do here.
+    }
 }
diff --git a/src/com/android/settings/location/SettingsInjector.java b/src/com/android/settings/location/SettingsInjector.java
index 22e2413..7bd190c 100644
--- a/src/com/android/settings/location/SettingsInjector.java
+++ b/src/com/android/settings/location/SettingsInjector.java
@@ -60,6 +60,10 @@
 class SettingsInjector {
     static final String TAG = "SettingsInjector";
 
+    /**
+     * If reading the status of a setting takes longer than this, we go ahead and start reading
+     * the next setting.
+     */
     private static final long INJECTED_STATUS_UPDATE_TIMEOUT_MILLIS = 1000;
 
     /**
@@ -219,9 +223,6 @@
 
     /**
      * Gets a list of preferences that other apps have injected.
-     *
-     * TODO: extract InjectedLocationSettingGetter that returns an iterable over
-     * InjectedSetting objects, so that this class can focus on UI
      */
     public List<Preference> getInjectedSettings() {
         Iterable<InjectedSetting> settings = getSettings();
@@ -273,62 +274,83 @@
     private final class StatusLoadingHandler extends Handler {
 
         /**
-         * Settings whose status values need to be loaded. A set is used to prevent redundant loads
-         * even if {@link #reloadStatusMessages()} is called many times in rapid succession (for
-         * example, if we receive a lot of {@link
-         * android.location.SettingInjectorService#ACTION_INJECTED_SETTING_CHANGED} broadcasts).
-         * <p/>
-         * We use a linked hash set to ensure that when {@link #reloadStatusMessages()} is called,
-         * any settings that haven't been loaded yet will finish loading before any already-loaded
-         * messages are loaded again.
+         * Settings whose status values need to be loaded. A set is used to prevent redundant loads.
          */
-        private LinkedHashSet<Setting> mSettingsToLoad = new LinkedHashSet<Setting>();
+        private Set<Setting> mSettingsToLoad = new HashSet<Setting>();
 
         /**
-         * Whether we're in the middle of loading settings.
+         * Settings that are being loaded now and haven't timed out. In practice this should have
+         * zero or one elements.
          */
-        private boolean mLoading;
+        private Set<Setting> mSettingsBeingLoaded = new HashSet<Setting>();
+
+        /**
+         * Settings that are being loaded but have timed out. If only one setting has timed out, we
+         * will go ahead and start loading the next setting so that one slow load won't delay the
+         * load of the other settings.
+         */
+        private Set<Setting> mTimedOutSettings = new HashSet<Setting>();
+
+        private boolean mReloadRequested;
 
         @Override
         public void handleMessage(Message msg) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "handleMessage start: " + msg + ", mSettingsToLoad: " + mSettingsToLoad);
+                Log.d(TAG, "handleMessage start: " + msg + ", " + this);
             }
 
+            // Update state in response to message
             switch (msg.what) {
                 case WHAT_RELOAD:
-                    mSettingsToLoad.addAll(mSettings);
-                    if (mLoading) {
-                        // Already waiting for a service to return its status, don't ask a new one
-                        return;
-                    }
-                    mLoading = true;
-                    break;
-                case WHAT_TIMEOUT:
-                    if (Log.isLoggable(TAG, Log.WARN)) {
-                        final Setting setting = (Setting) msg.obj;
-                        setting.timedOut = true;
-                        Log.w(TAG, "Timed out trying to get status for: " + setting);
-                    }
+                    mReloadRequested = true;
                     break;
                 case WHAT_RECEIVED_STATUS:
-                    final Setting setting = (Setting) msg.obj;
-                    if (setting.timedOut) {
-                        // We've already restarted retrieving the next setting, don't start another
-                        return;
+                    final Setting receivedSetting = (Setting) msg.obj;
+                    mSettingsBeingLoaded.remove(receivedSetting);
+                    mTimedOutSettings.remove(receivedSetting);
+                    removeMessages(WHAT_TIMEOUT, receivedSetting);
+                    break;
+                case WHAT_TIMEOUT:
+                    final Setting timedOutSetting = (Setting) msg.obj;
+                    mSettingsBeingLoaded.remove(timedOutSetting);
+                    mTimedOutSettings.add(timedOutSetting);
+                    if (Log.isLoggable(TAG, Log.WARN)) {
+                        Log.w(TAG, "Timed out trying to get status for: " + timedOutSetting);
                     }
-
-                    // Received the setting without timeout, clear any previous timed out status
-                    setting.timedOut = false;
                     break;
                 default:
-                    throw new IllegalArgumentException("Unexpected what: " + msg);
+                    Log.wtf(TAG, "Unexpected what: " + msg);
+            }
+
+            // Decide whether to load additional settings based on the new state. Start by seeing
+            // if we have headroom to load another setting.
+            if (mSettingsBeingLoaded.size() > 0 || mTimedOutSettings.size() > 1) {
+                // Don't load any more settings until one of the pending settings has completed.
+                // To reduce memory pressure, we want to be loading at most one setting (plus at
+                // most one timed-out setting) at a time. This means we'll be responsible for
+                // bringing in at most two services.
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "too many services already live for " + msg + ", " + this);
+                }
+                return;
+            }
+
+            if (mReloadRequested && mSettingsToLoad.isEmpty() && mSettingsBeingLoaded.isEmpty()
+                    && mTimedOutSettings.isEmpty()) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "reloading because idle and reload requesteed " + msg + ", " + this);
+                }
+                // Reload requested, so must reload all settings
+                mSettingsToLoad.addAll(mSettings);
+                mReloadRequested = false;
             }
 
             // Remove the next setting to load from the queue, if any
             Iterator<Setting> iter = mSettingsToLoad.iterator();
             if (!iter.hasNext()) {
-                mLoading = false;
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "nothing left to do for " + msg + ", " + this);
+                }
                 return;
             }
             Setting setting = iter.next();
@@ -337,17 +359,28 @@
             // Request the status value
             Intent intent = setting.createUpdatingIntent();
             mContext.startService(intent);
+            mSettingsBeingLoaded.add(setting);
 
             // Ensure that if receiving the status value takes too long, we start loading the
             // next value anyway
             Message timeoutMsg = obtainMessage(WHAT_TIMEOUT, setting);
-            removeMessages(WHAT_TIMEOUT);
             sendMessageDelayed(timeoutMsg, INJECTED_STATUS_UPDATE_TIMEOUT_MILLIS);
 
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "handleMessage end: " + msg + ", mSettingsToLoad: " + mSettingsToLoad);
+                Log.d(TAG, "handleMessage end " + msg + ", " + this
+                        + ", started loading " + setting);
             }
         }
+
+        @Override
+        public String toString() {
+            return "StatusLoadingHandler{" +
+                    "mSettingsToLoad=" + mSettingsToLoad +
+                    ", mSettingsBeingLoaded=" + mSettingsBeingLoaded +
+                    ", mTimedOutSettings=" + mTimedOutSettings +
+                    ", mReloadRequested=" + mReloadRequested +
+                    '}';
+        }
     }
 
     /**
@@ -357,7 +390,6 @@
 
         public final InjectedSetting setting;
         public final Preference preference;
-        public boolean timedOut = false;
 
         private Setting(InjectedSetting setting, Preference preference) {
             this.setting = setting;
@@ -369,11 +401,24 @@
             return "Setting{" +
                     "setting=" + setting +
                     ", preference=" + preference +
-                    ", timedOut=" + timedOut +
                     '}';
         }
 
         /**
+         * Returns true if they both have the same {@link #setting} value. Ignores mutable
+         * preference so that it's safe to use in sets.
+         */
+        @Override
+        public boolean equals(Object o) {
+            return this == o || o instanceof Setting && setting.equals(((Setting) o).setting);
+        }
+
+        @Override
+        public int hashCode() {
+            return setting.hashCode();
+        }
+
+        /**
          * Creates an Intent to ask the receiver for the current status for the setting, and display
          * it when it replies.
          */
diff --git a/src/com/android/settings/nfc/PaymentBackend.java b/src/com/android/settings/nfc/PaymentBackend.java
index fc0f4a3..3c2c3ce 100644
--- a/src/com/android/settings/nfc/PaymentBackend.java
+++ b/src/com/android/settings/nfc/PaymentBackend.java
@@ -86,17 +86,4 @@
                 Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
                 app != null ? app.flattenToString() : null);
     }
-
-    public boolean isAutoPaymentMode() {
-        String mode = Settings.Secure.getString(mContext.getContentResolver(),
-                Settings.Secure.NFC_PAYMENT_MODE);
-        return (!CardEmulationManager.PAYMENT_MODE_MANUAL.equals(mode));
-    }
-
-    public void setAutoPaymentMode(boolean enable) {
-        Settings.Secure.putString(mContext.getContentResolver(),
-                Settings.Secure.NFC_PAYMENT_MODE,
-                enable ? CardEmulationManager.PAYMENT_MODE_AUTO
-                       : CardEmulationManager.PAYMENT_MODE_MANUAL);
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/nfc/PaymentDefaultDialog.java b/src/com/android/settings/nfc/PaymentDefaultDialog.java
index 2dd465a..a6887a3 100644
--- a/src/com/android/settings/nfc/PaymentDefaultDialog.java
+++ b/src/com/android/settings/nfc/PaymentDefaultDialog.java
@@ -62,7 +62,6 @@
         switch (which) {
             case BUTTON_POSITIVE:
                 mBackend.setDefaultPaymentApp(mNewDefault);
-                mBackend.setAutoPaymentMode(true);
                 setResult(RESULT_OK);
                 break;
             case BUTTON_NEGATIVE:
@@ -98,7 +97,6 @@
         }
 
         // Get current mode and default component
-        boolean isAuto = mBackend.isAutoPaymentMode();
         ComponentName defaultComponent = mBackend.getDefaultPaymentApp();
         if (defaultComponent != null && defaultComponent.equals(component)) {
             Log.e(TAG, "Component " + component + " is already default.");
@@ -128,7 +126,7 @@
         // Compose dialog; get
         final AlertController.AlertParams p = mAlertParams;
         p.mTitle = getString(R.string.nfc_payment_set_default);
-        if (defaultAppInfo == null || !isAuto) {
+        if (defaultAppInfo == null) {
             p.mMessage = "Always use " + newAppInfo.loadLabel(pm) + " when you tap and pay?";
         } else {
             p.mMessage = "Always use " + newAppInfo.loadLabel(pm) + " instead of " +
diff --git a/src/com/android/settings/nfc/PaymentSettings.java b/src/com/android/settings/nfc/PaymentSettings.java
index a1ed883..41bcc2c 100644
--- a/src/com/android/settings/nfc/PaymentSettings.java
+++ b/src/com/android/settings/nfc/PaymentSettings.java
@@ -48,8 +48,6 @@
         PreferenceManager manager = getPreferenceManager();
         PreferenceScreen screen = manager.createPreferenceScreen(getActivity());
 
-        boolean isAuto = mPaymentBackend.isAutoPaymentMode();
-
         // Get all payment services
         List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos();
         if (appInfos != null && appInfos.size() > 0) {
@@ -58,7 +56,6 @@
                 PaymentAppPreference preference =
                         new PaymentAppPreference(getActivity(), appInfo, this);
                 // If for some reason isAuto gets out of sync, clear out app default
-                appInfo.isDefault &= isAuto;
                 preference.setIcon(appInfo.icon);
                 preference.setTitle(appInfo.caption);
                 screen.addPreference(preference);
@@ -67,7 +64,7 @@
                 PaymentAppInfo appInfo = new PaymentAppInfo();
                 appInfo.icon = null;
                 appInfo.componentName = null;
-                appInfo.isDefault = !isAuto;
+                appInfo.isDefault = !(mPaymentBackend.getDefaultPaymentApp() != null);
                 // Add "Ask every time" option
                 PaymentAppPreference preference =
                         new PaymentAppPreference(getActivity(), appInfo, this);
@@ -85,10 +82,8 @@
             PaymentAppInfo appInfo = (PaymentAppInfo) v.getTag();
             if (appInfo.componentName != null) {
                 mPaymentBackend.setDefaultPaymentApp(appInfo.componentName);
-                mPaymentBackend.setAutoPaymentMode(true);
             } else {
                 mPaymentBackend.setDefaultPaymentApp(null);
-                mPaymentBackend.setAutoPaymentMode(false);
             }
             refresh();
         }
diff --git a/src/com/android/settings/widget/SettingsAppWidgetProvider.java b/src/com/android/settings/widget/SettingsAppWidgetProvider.java
index da085f7..bf3f497 100644
--- a/src/com/android/settings/widget/SettingsAppWidgetProvider.java
+++ b/src/com/android/settings/widget/SettingsAppWidgetProvider.java
@@ -548,9 +548,23 @@
                     final UserManager um =
                             (UserManager) context.getSystemService(Context.USER_SERVICE);
                     if (!um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) {
-                        int mode = desiredState
-                                ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
-                                : Settings.Secure.LOCATION_MODE_BATTERY_SAVING;
+                        int currentMode = Settings.Secure.getInt(resolver,
+                                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
+                        int mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+                        switch (currentMode) {
+                            case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
+                                mode = Settings.Secure.LOCATION_MODE_BATTERY_SAVING;
+                                break;
+                            case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
+                                mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+                                break;
+                            case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
+                                mode = Settings.Secure.LOCATION_MODE_OFF;
+                                break;
+                            case Settings.Secure.LOCATION_MODE_OFF:
+                                mode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+                                break;
+                        }
                         Settings.Secure.putInt(resolver, Settings.Secure.LOCATION_MODE, mode);
                         return desiredState;
                     }