Code drop from //branches/cupcake/...@124589
diff --git a/src/com/android/settings/AirplaneModeEnabler.java b/src/com/android/settings/AirplaneModeEnabler.java
index f47d679..0bd950b 100644
--- a/src/com/android/settings/AirplaneModeEnabler.java
+++ b/src/com/android/settings/AirplaneModeEnabler.java
@@ -100,9 +100,10 @@
      */
     private void onAirplaneModeChanged() {
         ServiceState serviceState = mPhoneStateReceiver.getServiceState();
-        boolean isPhoneOff = serviceState.getState() == ServiceState.STATE_POWER_OFF;
-        mCheckBoxPref.setChecked(isPhoneOff);
-        mCheckBoxPref.setSummary(R.string.airplane_mode_summary);            
+        boolean airplaneModeEnabled = serviceState.getState() == ServiceState.STATE_POWER_OFF;
+        mCheckBoxPref.setChecked(airplaneModeEnabled);
+        mCheckBoxPref.setSummary(airplaneModeEnabled ? null : 
+                mContext.getString(R.string.airplane_mode_summary));            
         mCheckBoxPref.setEnabled(true);
     }
     
diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java
index 9b74eea..f1fa2ef 100644
--- a/src/com/android/settings/ApnEditor.java
+++ b/src/com/android/settings/ApnEditor.java
@@ -63,6 +63,8 @@
     private EditTextPreference mMmsProxy;
     private EditTextPreference mMmsPort;
     private EditTextPreference mApnType;
+    private String mCurMnc;
+    private String mCurMcc;
     
     private Uri mUri;
     private Cursor mCursor;
@@ -210,6 +212,8 @@
                     // Auto populate MNC and MCC for new entries, based on what SIM reports
                     mMcc.setText(mcc);
                     mMnc.setText(mnc);
+                    mCurMnc = mnc;
+                    mCurMcc = mcc;
                 }
             }
         }
@@ -338,6 +342,12 @@
         
         values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
         
+        if (mCurMnc != null && mCurMcc != null) {
+            if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) {
+                values.put(Telephony.Carriers.CURRENT, 1);
+            }
+        }
+        
         getContentResolver().update(mUri, values, null, null);
         
         return true;
diff --git a/src/com/android/settings/ApnSettings.java b/src/com/android/settings/ApnSettings.java
index ea13ee7..83efa3f 100644
--- a/src/com/android/settings/ApnSettings.java
+++ b/src/com/android/settings/ApnSettings.java
@@ -27,6 +27,7 @@
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceScreen;
 import android.provider.Telephony;
+import android.text.TextUtils;
 import android.view.Menu;
 import android.view.MenuItem;
 
@@ -68,12 +69,16 @@
             String name = mCursor.getString(NAME_INDEX);
             String apn = mCursor.getString(APN_INDEX);
             
-            Preference pref = new Preference((Context) this);
-            pref.setKey(mCursor.getString(ID_INDEX));
-            pref.setTitle(name);
-            pref.setSummary(apn);
-            pref.setPersistent(false);
-            apnList.addPreference(pref);
+            if (name != null && apn != null && TextUtils.getTrimmedLength(name) > 0
+                    && TextUtils.getTrimmedLength(apn) > 0) {
+                Preference pref = new Preference((Context) this);
+                pref.setKey(mCursor.getString(ID_INDEX));
+                pref.setTitle(name);
+                pref.setSummary(apn);
+                pref.setPersistent(false);
+                apnList.addPreference(pref);
+            }
+            
             mCursor.moveToNext();
         }
     }
diff --git a/src/com/android/settings/ApplicationSettings.java b/src/com/android/settings/ApplicationSettings.java
index 0ee2789..85fe11f 100644
--- a/src/com/android/settings/ApplicationSettings.java
+++ b/src/com/android/settings/ApplicationSettings.java
@@ -18,6 +18,7 @@
 
 import android.app.AlertDialog;
 import android.content.DialogInterface;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
@@ -29,6 +30,7 @@
         DialogInterface.OnClickListener {
     
     private static final String KEY_TOGGLE_INSTALL_APPLICATIONS = "toggle_install_applications";
+    private static final String KEY_QUICK_LAUNCH = "quick_launch";
 
     private CheckBoxPreference mToggleAppInstallation;
     
@@ -42,7 +44,12 @@
 
         mToggleAppInstallation = (CheckBoxPreference) findPreference(KEY_TOGGLE_INSTALL_APPLICATIONS);
         mToggleAppInstallation.setChecked(isNonMarketAppsAllowed());
-        
+
+        if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_NOKEYS) {
+            // No hard keyboard, remove the setting for quick launch
+            Preference quickLaunchSetting = findPreference(KEY_QUICK_LAUNCH);
+            getPreferenceScreen().removePreference(quickLaunchSetting);
+        }
     }
 
     @Override
@@ -68,13 +75,13 @@
 
     private void setNonMarketAppsAllowed(boolean enabled) {
         // Change the system setting
-        Settings.System.putInt(getContentResolver(), Settings.System.INSTALL_NON_MARKET_APPS, 
+        Settings.Secure.putInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, 
                                 enabled ? 1 : 0);
     }
     
     private boolean isNonMarketAppsAllowed() {
-            return Settings.System.getInt(getContentResolver(), 
-                                           Settings.System.INSTALL_NON_MARKET_APPS, 0) > 0;
+        return Settings.Secure.getInt(getContentResolver(), 
+                                      Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;
     }
 
     private void warnAppInstallation() {
diff --git a/src/com/android/settings/BatteryInfo.java b/src/com/android/settings/BatteryInfo.java
index ef60fc3..ed3e3e3 100644
--- a/src/com/android/settings/BatteryInfo.java
+++ b/src/com/android/settings/BatteryInfo.java
@@ -29,7 +29,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.pim.DateUtils;
+import android.text.format.DateUtils;
 import android.widget.TextView;
 
 import com.android.internal.app.IBatteryStats;
diff --git a/src/com/android/settings/BluetoothDataEntry.java b/src/com/android/settings/BluetoothDataEntry.java
deleted file mode 100644
index 1c7e0b6..0000000
--- a/src/com/android/settings/BluetoothDataEntry.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnKeyListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-public class BluetoothDataEntry extends Activity implements OnKeyListener {
-    
-    private Bundle extras;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        setContentView(R.layout.bluetooth_data_entry);
-
-        mDataLabel = (TextView)findViewById(R.id.dataLabel);
-        mDataEntry = (EditText)findViewById(R.id.dataEntry);
-        mConfirmButton = (Button)findViewById(R.id.confirmButton);
-        mCancelButton = (Button)findViewById(R.id.cancelButton);
-
-        mDataEntry.setOnKeyListener(this);
-        Intent intent = getIntent();
-        String label = null;
-        {
-            String labelExtra = intent.getStringExtra("label");
-            if (labelExtra != null) {
-                label = labelExtra;
-            }
-        }
-        extras = intent.getBundleExtra("extras");
-        if (label != null && label.length() > 0) {
-            mDataLabel.setText(label);
-        }
-
-        mConfirmButton.setOnClickListener(new ConfirmButtonListener());
-        mCancelButton.setOnClickListener(new CancelButtonListener());
-    }
-
-    private class ConfirmButtonListener implements OnClickListener {
-        public void onClick(View v) {
-            activityResult(RESULT_OK, mDataEntry.getText().toString(), extras);
-        }
-    }
-
-    private class CancelButtonListener implements OnClickListener {
-        public void onClick(View v) {
-            activityResult(RESULT_CANCELED, null, null);
-        }
-    }
-
-    protected void activityResult(int result, String data, Bundle extras) {
-        setResult(result, (new Intent()).setAction(data).putExtras(extras));
-        finish();
-    }
-
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER 
-                || keyCode == KeyEvent.KEYCODE_ENTER) {
-            activityResult(RESULT_OK, mDataEntry.getText().toString(), extras);
-            return true;
-        }
-        return false;
-    }
-
-    protected TextView mDataLabel;
-    protected EditText mDataEntry;
-    protected Button mConfirmButton;
-    protected Button mCancelButton;
-}
diff --git a/src/com/android/settings/BluetoothListItem.java b/src/com/android/settings/BluetoothListItem.java
deleted file mode 100644
index c75be13..0000000
--- a/src/com/android/settings/BluetoothListItem.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.android.settings;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import java.util.Map;
-
-/**
- * This class extends Preference to display bluetooth status icons. One
- * icon specifies the connection/pairing status that is right-aligned.
- * An optional headset icon can be added to its left as well.
- */
-public class BluetoothListItem extends Preference {
-
-    private boolean mIsHeadset;
-    private int mWeight;
-    
-    public BluetoothListItem(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setWidgetLayoutResource(R.layout.preference_widget_btdevice_status);
-    }
-
-    private void updateIcons(View view) {
-        ImageView headsetView = (ImageView) view.findViewById(R.id.device_headset);
-        headsetView.setVisibility(mIsHeadset ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    public void onBindView(View view) {
-        super.onBindView(view);
-        updateIcons(view);
-    }
-
-    /**
-     * Set whether the device is of headset type
-     * @param headset whether or not the headset icon should be shown
-     */
-    public void setHeadset(boolean headset) {
-        mIsHeadset = headset;
-        notifyChanged();
-    }
-
-    /**
-     * Sets the weight for ordering by signal strength or importance
-     * @param weight the ordering weight
-     */
-    public void setWeight(int weight) {
-        mWeight = weight;
-    }
-
-    /**
-     * Returns the currently set ordering weight
-     * @return the current ordering weight
-     */
-    public int getWeight() {
-        return mWeight;
-    }
-    
-    @Override
-    public int compareTo(Preference another) {
-        int diff = ((BluetoothListItem)another).mWeight - mWeight;
-        // Let the new one be after the old one, if they are the same weight
-        // TODO: Implement a more reliable way to consistently order items of
-        // the same weight
-        if (diff == 0) diff = 1;
-        return diff;
-    }
-}
diff --git a/src/com/android/settings/BluetoothPINEntry.java b/src/com/android/settings/BluetoothPINEntry.java
deleted file mode 100644
index 8933c03..0000000
--- a/src/com/android/settings/BluetoothPINEntry.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.app.NotificationManager;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-public class BluetoothPINEntry extends BluetoothDataEntry {
-    private BluetoothDevice mBluetooth;
-    private String mAddress;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        Intent intent = getIntent();
-        if (!intent.getAction().equals(BluetoothIntent.PAIRING_REQUEST_ACTION))
-        {
-            Log.e(this.getClass().getName(),
-                  "Error: this activity may be started only with intent " +
-                  BluetoothIntent.PAIRING_REQUEST_ACTION);
-            finish();
-        }
-        
-        // Cancel the notification, if any
-        NotificationManager manager = (NotificationManager) 
-        getSystemService(Context.NOTIFICATION_SERVICE);
-        manager.cancel(0xb100ceee);
-        
-        mAddress = intent.getStringExtra(BluetoothIntent.ADDRESS);
-
-        mBluetooth = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
-        
-        String remoteName = mBluetooth.getRemoteName(mAddress);
-        if (remoteName == null) {
-            remoteName = mAddress;
-        }
-            
-        mDataLabel.setText(getString(R.string.bluetooth_enter_pin_msg) + remoteName);
-    }
-
-    @Override
-    public void activityResult(int result, String data, Bundle extras) {
-        switch (result) {
-        case RESULT_OK:
-            byte[] pin = BluetoothDevice.convertPinToBytes(mDataEntry.getText().toString());
-            if (pin == null) {
-                return;
-            }
-            mBluetooth.setPin(mAddress, pin);
-            break;
-        case RESULT_CANCELED:
-            mBluetooth.cancelPin(mAddress);
-            break;
-        }
-        finish();
-    }
-}
diff --git a/src/com/android/settings/BluetoothPinRequest.java b/src/com/android/settings/BluetoothPinRequest.java
deleted file mode 100644
index d6a12f3..0000000
--- a/src/com/android/settings/BluetoothPinRequest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2008 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.settings;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-
-/**
- * This class handles the Bluetooth pairing PIN request from the bluetooth service
- * It checks if the BluetoothSettings activity is currently visible and lets that
- * activity handle the request. Otherwise it puts a Notification in the status bar,
- * which can be clicked to bring up the PIN entry dialog. 
- */
-public class BluetoothPinRequest extends BroadcastReceiver {
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (intent.getAction().equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
-            if (BluetoothSettings.isRunning()) {
-                // Let the BluetoothSettings activity handle it
-                return;
-            } else {
-                Resources res = context.getResources();
-                String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
-                Notification pair = new Notification(
-                        android.R.drawable.stat_sys_data_bluetooth,
-                        res.getString(R.string.bluetooth_notif_ticker),
-                        System.currentTimeMillis());
-
-                Intent pinIntent = new Intent();
-                pinIntent.setClass(context, BluetoothPINEntry.class);
-                pinIntent.putExtra(BluetoothIntent.ADDRESS, address); 
-                pinIntent.setAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
-                PendingIntent pending = PendingIntent.getActivity(context, 0, 
-                        pinIntent, PendingIntent.FLAG_ONE_SHOT);
-                
-                String name = intent.getStringExtra(BluetoothIntent.NAME);
-                
-                if (name == null) {
-                    BluetoothDevice bluetooth = 
-                        (BluetoothDevice)context.getSystemService(Context.BLUETOOTH_SERVICE);
-                    name = bluetooth.getRemoteName(address);
-                    if (name == null) {
-                        name = address;
-                    }
-                }
-                
-                pair.setLatestEventInfo(context, 
-                        res.getString(R.string.bluetooth_notif_title), 
-                        res.getString(R.string.bluetooth_notif_message) + name, 
-                        pending);
-                
-                NotificationManager manager = (NotificationManager) 
-                        context.getSystemService(Context.NOTIFICATION_SERVICE);
-                manager.notify(0xb100ceee, pair);
-            }
-        }
-    }
-}
diff --git a/src/com/android/settings/BluetoothSettings.java b/src/com/android/settings/BluetoothSettings.java
deleted file mode 100644
index 6a19ec4..0000000
--- a/src/com/android/settings/BluetoothSettings.java
+++ /dev/null
@@ -1,1035 +0,0 @@
-/*
- * Copyright (C) 2008 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.settings;
-
-import android.app.AlertDialog;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothIntent;
-import android.bluetooth.DeviceClass;
-import android.bluetooth.IBluetoothDeviceCallback;
-import android.bluetooth.IBluetoothHeadsetCallback;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceScreen;
-import android.provider.Settings;
-import android.text.method.PasswordTransformationMethod;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View.OnKeyListener;
-import android.widget.AdapterView;
-import android.widget.EditText;
-import android.widget.Toast;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-
-import java.util.HashMap;
-
-public class BluetoothSettings
-        extends PreferenceActivity
-        implements OnSharedPreferenceChangeListener, OnKeyListener,
-                View.OnCreateContextMenuListener {
-
-    private static final String TAG = "BluetoothSettings";
-
-    private static final int MENU_SCAN_ID = Menu.FIRST;
-    private static final int MENU_CLEAR_ID = Menu.FIRST + 1;
-    
-    private static final int MENU_CONNECT = ContextMenu.FIRST;
-    private static final int MENU_DISCONNECT = ContextMenu.FIRST + 1;
-    private static final int MENU_PAIR = ContextMenu.FIRST + 2;
-    private static final int MENU_UNPAIR = ContextMenu.FIRST + 3;
-    
-    private static final String BT_ENABLE = "bt_checkbox";
-    private static final String BT_VISIBILITY = "bt_visibility";
-    private static final String BT_NAME = "bt_name";
-    
-    private static final String BT_KEY_PREFIX = "bt_dev_";
-    private static final int BT_KEY_LENGTH = BT_KEY_PREFIX.length();
-    private static final String FREEZE_ADDRESSES = "addresses";
-    private static final String FREEZE_TYPES = "types";
-    private static final String FREEZE_PIN = "pinText";
-    private static final String FREEZE_PIN_ADDRESS = "pinAddress";
-    private static final String FREEZE_RSSI = "rssi";
-    private static final String FREEZE_DISCOVERABLE_START = "dstart";
-    
-    private static final int HANDLE_FAILED_TO_CONNECT = 1;
-    private static final int HANDLE_CONNECTING = 2;
-    private static final int HANDLE_CONNECTED = 3;
-    private static final int HANDLE_DISCONNECTED = 4;
-    private static final int HANDLE_PIN_REQUEST = 5;
-    private static final int HANDLE_DISCOVERABLE_TIMEOUT = 6;
-    private static final int HANDLE_INITIAL_SCAN = 7;
-    private static final int HANDLE_PAIRING_FAILED = 8;
-    private static final int HANDLE_PAIRING_PASSED = 9;
-    private static final int HANDLE_PAUSE_TIMEOUT = 10;
-    
-
-    
-    private static String STR_CONNECTED;
-    private static String STR_PAIRED;
-    private static String STR_PAIRED_NOT_NEARBY;
-    private static String STR_NOT_CONNECTED;
-    private static String STR_CONNECTING;
-    private static String STR_PAIRING;
-    
-    private static final int WEIGHT_CONNECTED = 1;
-    private static final int WEIGHT_PAIRED = 0;
-    private static final int WEIGHT_UNKNOWN = -1;
-    
-    private CheckBoxPreference mBTToggle;
-    private CheckBoxPreference mBTVisibility;
-    private EditTextPreference mBTName;
-    private ProgressCategory mBTDeviceList;
-    private AlertDialog mPinDialog;
-    private String      mPinAddress;
-    private EditText    mPinEdit;
-    private View        mPinButton1;
-    private String      mDisconnectAddress;
-    
-    private BluetoothDevice mBluetooth;
-    private BluetoothHeadset mBluetoothHeadset;
-    private boolean mIsEnabled;
-    private String mLastConnected;
-    private static boolean sIsRunning;
-    private static DeviceCallback sDeviceCallback;    
-    private IntentFilter mIntentFilter;
-    private Resources mRes;
-    private long mDiscoverableStartTime;
-    private int mDiscoverableTime;
-    private static final String DISCOVERABLE_TIME = "debug.bt.discoverable_time";
-    private static final int DISCOVERABLE_TIME_DEFAULT = 120;
-    private boolean mAutoDiscovery;
-    // After a few seconds after a pause, if the user doesn't restart the 
-    // BT settings, then we need to cleanup a few things in the message handler
-    private static final int PAUSE_TIMEOUT = 3000;
-    
-    private boolean mStartScan;
-    private static final String AUTO_DISCOVERY = "debug.bt.auto_discovery";
-    private HashMap<String,Preference> mDeviceMap;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        
-        addPreferencesFromResource(R.xml.bluetooth_settings);
-    
-        // Deal with restarted activities by passing a static callback object to
-        // the Bluetooth service
-        if (sDeviceCallback == null) {
-            sDeviceCallback = new DeviceCallback();
-        }
-        sDeviceCallback.setHandler(mHandler);
-        
-        mDiscoverableTime = SystemProperties.getInt(DISCOVERABLE_TIME, -1);
-        if (mDiscoverableTime <= 0) {
-            mDiscoverableTime= DISCOVERABLE_TIME_DEFAULT;
-        }
-        mAutoDiscovery = SystemProperties.getBoolean(AUTO_DISCOVERY, true);
-
-        if (!initBluetoothAPI()) {
-            finish();
-            return;
-        }
-        initUI();
-        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
-        
-        mDeviceMap = new HashMap<String,Preference>();
-        if (icicle != null && icicle.containsKey(FREEZE_ADDRESSES)) {
-            addDevices(icicle.getStringArray(FREEZE_ADDRESSES), 
-                    icicle.getStringArray(FREEZE_TYPES), 
-                    icicle.getIntArray(FREEZE_RSSI));
-            if (icicle.containsKey(FREEZE_PIN)) {
-                String savedPin = icicle.getString(FREEZE_PIN);
-                String pinAddress = icicle.getString(FREEZE_PIN_ADDRESS);
-                mPinDialog = showPinDialog(savedPin, pinAddress);
-            }
-            mDiscoverableStartTime = icicle.getLong(FREEZE_DISCOVERABLE_START);
-        } else {
-            mStartScan = true;
-        }
-    }
-
-    private void initUI() {
-        mBTToggle = (CheckBoxPreference) findPreference(BT_ENABLE);
-        mBTVisibility = (CheckBoxPreference) findPreference(BT_VISIBILITY);
-        mBTName = (EditTextPreference) findPreference(BT_NAME);
-        mBTDeviceList = (ProgressCategory) findPreference("bt_device_list");
-        mBTDeviceList.setOrderingAsAdded(false);
-        mRes = getResources();
-        if (mIsEnabled) {
-            String name = mBluetooth.getName();
-            if (name != null) {
-                mBTName.setSummary(name);
-            }
-        }
-        mBTVisibility.setEnabled(mIsEnabled);
-        mBTName.setEnabled(mIsEnabled);
-        STR_CONNECTED = mRes.getString(R.string.bluetooth_connected);
-        STR_PAIRED = mRes.getString(R.string.bluetooth_paired);
-        STR_PAIRED_NOT_NEARBY = 
-            mRes.getString(R.string.bluetooth_paired_not_nearby);
-        STR_CONNECTING = mRes.getString(R.string.bluetooth_connecting);
-        STR_PAIRING = mRes.getString(R.string.bluetooth_pairing);
-        STR_NOT_CONNECTED = mRes.getString(R.string.bluetooth_not_connected);
-        getListView().setOnCreateContextMenuListener(this);
-    }
-    
-    private boolean initBluetoothAPI() {
-        mIntentFilter =
-            new IntentFilter(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.BONDING_CREATED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.ENABLED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.DISCOVERY_STARTED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.MODE_CHANGED_ACTION);
-        
-        mBluetooth = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
-        mBluetoothHeadset = new BluetoothHeadset(this);
-        if (mBluetooth == null) { // If the environment doesn't support BT
-            return false;
-        }
-        mIsEnabled = mBluetooth.isEnabled();
-        return true;
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        
-        sIsRunning = true;
-        mHandler.removeMessages(HANDLE_PAUSE_TIMEOUT);
-        registerReceiver(mReceiver, mIntentFilter);
-
-        mIsEnabled = mBluetooth.isEnabled();
-        updateStatus();
-        final boolean discoverable = mBluetooth.getMode() == 
-            BluetoothDevice.MODE_DISCOVERABLE;
-        mBTDeviceList.setProgress(mIsEnabled && mBluetooth.isDiscovering());
-        mBTVisibility.setChecked(mIsEnabled && discoverable);
-        
-        if (discoverable) {
-            mHandler.sendMessage(
-                    mHandler.obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT));            
-        }
-        
-        if (mIsEnabled && mStartScan) {
-            // First attempt after 100ms
-            mHandler.sendMessageDelayed(
-                    mHandler.obtainMessage(HANDLE_INITIAL_SCAN, 1), 100);
-        }
-        mStartScan = false;
-        
-        // Check if headset status changed since we paused
-        String connected = mBluetoothHeadset.getHeadsetAddress();
-        if (connected != null) {
-            updateRemoteDeviceStatus(connected);
-        }
-        if (mLastConnected != null) {
-            updateRemoteDeviceStatus(mLastConnected);
-        }
-    }
-    
-    @Override
-    protected void onPause() {
-        sIsRunning = false;
-
-        unregisterReceiver(mReceiver);
-
-        // Wait for a few seconds and cleanup any pending requests, states
-        mHandler.sendMessageDelayed(
-                mHandler.obtainMessage(HANDLE_PAUSE_TIMEOUT, 
-                        new Object[] { mBluetooth, mPinAddress }), 
-                PAUSE_TIMEOUT);
-        super.onPause();
-    }
-
-    @Override
-    protected void onDestroy() {
-        mBluetoothHeadset.close();
-        sDeviceCallback.setHandler(null);
-        
-        super.onDestroy();
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration c) {
-        super.onConfigurationChanged(c);
-        // Don't do anything on keyboardHidden/orientation change, as we need
-        // to make sure that we don't lose pairing request intents.
-    }
-    
-    public static boolean isRunning() {
-        return sIsRunning;
-    }
-    
-    @Override
-    protected void onSaveInstanceState(Bundle icicle) {
-        int deviceCount = mBTDeviceList.getPreferenceCount();
-        String [] addresses = new String[deviceCount];
-        String [] states = new String[deviceCount];
-        int [] weights = new int[deviceCount];
-        for (int i = 0; i < deviceCount; i++) {
-            BluetoothListItem p = (BluetoothListItem) mBTDeviceList.getPreference(i);
-            CharSequence summary = p.getSummary();
-            if (summary != null) {
-                states[i] = summary.toString();
-            } else {
-                states[i] = STR_NOT_CONNECTED;
-            }
-            addresses[i] = getAddressFromKey(p.getKey());
-            weights[i] = p.getWeight();
-        }
-        icicle.putStringArray(FREEZE_ADDRESSES, addresses);
-        icicle.putStringArray(FREEZE_TYPES, states);
-        icicle.putIntArray(FREEZE_RSSI, weights);
-        icicle.putLong(FREEZE_DISCOVERABLE_START, mDiscoverableStartTime);
-        if (mPinDialog != null && mPinDialog.isShowing()) {
-            icicle.putString(FREEZE_PIN, mPinEdit.getText().toString());
-            icicle.putString(FREEZE_PIN_ADDRESS, mPinAddress);
-        }
-        super.onSaveInstanceState(icicle);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        super.onCreateOptionsMenu(menu);
-        menu.add(0, MENU_SCAN_ID, 0, 
-                mRes.getString(R.string.bluetooth_scan_for_devices))
-            .setIcon(R.drawable.ic_menu_scan_bluetooth);
-        menu.add(0, MENU_CLEAR_ID, 0, 
-                mRes.getString(R.string.bluetooth_clear_list))
-            .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-        case MENU_SCAN_ID:
-            startScanning();
-            return true;
-        case MENU_CLEAR_ID:
-            clearDevices();
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-    
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
-        if (!(menuInfo instanceof AdapterContextMenuInfo)) {
-            return;
-        }
-        int position = ((AdapterContextMenuInfo)menuInfo).position;
-        Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(position);
-        if (!(pref instanceof BluetoothListItem)) {
-            return;
-        }
-        String address = getAddressFromKey(pref.getKey());
-        // Setup the menu header
-        String name = mBluetooth.getRemoteName(address);
-        menu.setHeaderTitle(name != null? name : address);
-        int n = 0;
-        if (mBluetoothHeadset.isConnected(address)) {
-            menu.add(0, MENU_DISCONNECT, n++, R.string.bluetooth_disconnect);            
-        } else {
-            menu.add(0, MENU_CONNECT, n++, R.string.bluetooth_connect);
-        }
-        if (mBluetooth.hasBonding(address)) {
-            menu.add(0, MENU_UNPAIR, n++, R.string.bluetooth_unpair);
-        } else {
-            menu.add(0, MENU_PAIR, n++, R.string.bluetooth_pair);
-        }
-    }
-
-    @Override
-    public boolean onContextItemSelected(MenuItem item) {
-        AdapterView.AdapterContextMenuInfo info;
-        if (!(item.getMenuInfo() instanceof AdapterContextMenuInfo)) {
-            return false;
-        }
-        info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
-        Preference pref = (Preference) getPreferenceScreen().getRootAdapter().
-                getItem(info.position);
-        String address = getAddressFromKey(pref.getKey());
-        mBluetooth.cancelDiscovery();
-        switch (item.getItemId()) {
-            case MENU_DISCONNECT:
-                if (mBluetoothHeadset.isConnected(address)) {
-                    mBluetoothHeadset.disconnectHeadset();
-                }
-                break;
-            case MENU_CONNECT:
-                if (!mBluetoothHeadset.isConnected(address)) {
-                    updateRemoteDeviceStatus(address, STR_CONNECTING); 
-                    connect(pref, address);
-                }
-                break;
-            case MENU_UNPAIR:
-                if (mBluetooth.hasBonding(address)) {
-                    mBluetooth.removeBonding(address);
-                    updateRemoteDeviceStatus(address);
-                }
-                break;
-            case MENU_PAIR:
-                if (!mBluetooth.hasBonding(address)) {
-                    pair(pref, address);
-                }
-                break;
-        }
-        return true;
-    }
-    
-    private void startScanning() {
-        if (mIsEnabled && mBluetooth.isDiscovering()) {
-            return;
-        }
-        resetDeviceListUI();
-        if (mIsEnabled) {
-            mBluetooth.startDiscovery();
-        }
-    }
-
-    private void clearDevices() {
-        String [] addresses = mBluetooth.listBondings();
-        if (addresses != null) {
-            for (int i = 0; i < addresses.length; i++) {
-                unbond(addresses[i]);
-            }
-        }
-        resetDeviceListUI();
-    }
-    
-    /* Update the Bluetooth toggle and visibility summary */
-    private void updateStatus() {
-        boolean started = mIsEnabled;
-        mBTToggle.setChecked(started);
-    }
-    
-    private void updateRemoteDeviceStatus(String address) {
-        if (address != null) {
-            Preference device = mDeviceMap.get(address);
-            if (device == null) {
-                // This device is not in our discovered list
-                // Let's add the device, if BT is not shut down already
-                if (mIsEnabled) {
-                    addDeviceToUI(address, null, null, WEIGHT_PAIRED);
-                }
-                return;
-            }
-            device.setEnabled(true);
-            if (address.equals(mBluetoothHeadset.getHeadsetAddress())) {
-                int state = mBluetoothHeadset.getState();
-                switch (state) {
-                    case BluetoothHeadset.STATE_CONNECTED:
-                        device.setSummary(STR_CONNECTED);
-                        mLastConnected = address;
-                        break;
-                    case BluetoothHeadset.STATE_CONNECTING:
-                        device.setSummary(STR_CONNECTING);
-                        break;
-                    case BluetoothHeadset.STATE_DISCONNECTED:
-                        if (mBluetooth.hasBonding(address)) {
-                            device.setSummary(STR_PAIRED);
-                        }
-                        break;
-                }
-            } else if (mBluetooth.hasBonding(address)) {
-                device.setSummary(STR_PAIRED);
-            } else {
-                device.setSummary(STR_NOT_CONNECTED);
-            }
-        }
-    }
-    
-    private void updateRemoteDeviceStatus(String address, String summary) {
-        Preference device = mDeviceMap.get(address);
-        if (device != null) {
-            device.setEnabled(true);
-            device.setSummary(summary);
-        }
-    }
-    
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-        if (key.equals(BT_NAME)) {
-            String name = sharedPreferences.getString(key, null);
-            if (name == null) {
-                return;
-            }
-            if (mBluetooth.setName(name)) {
-                mBTName.setSummary(name);
-            }
-        }
-    }
-        
-    private String getAddressFromKey(String key) {
-        if (key != null) {
-            return key.substring(BT_KEY_LENGTH);
-        }
-        return "";
-    }
-
-    private void sendPin(String pin) {
-        byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
-        if (pinBytes == null) {
-            mBluetooth.cancelPin(mPinAddress);
-        } else {
-            mBluetooth.setPin(mPinAddress, pinBytes);
-        }
-        mPinAddress = null;
-    }
-    
-    private AlertDialog showPinDialog(String savedPin, String pinAddress) {
-        if (mPinDialog != null) {
-            return mPinDialog;
-        }
-        View view = LayoutInflater.from(this).inflate(
-                R.layout.bluetooth_pin_entry, null);
-        mPinEdit = (EditText) view.findViewById(R.id.text);
-        mPinEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
-        mPinEdit.setOnKeyListener(this);
-        mPinAddress = pinAddress;
-        
-        if (savedPin != null) {
-            mPinEdit.setText(savedPin);
-        }
-        
-        String remoteName = mBluetooth.getRemoteName(mPinAddress);
-        if (remoteName == null) {
-            remoteName = mPinAddress;
-        }
-            
-        AlertDialog ad = new AlertDialog.Builder(this)
-            .setTitle(getString(R.string.bluetooth_notif_title))
-            .setMessage(getString(R.string.bluetooth_enter_pin_msg) + remoteName)
-            .setView(view)
-            .setPositiveButton(android.R.string.ok, mDisconnectListener)
-            .setNegativeButton(android.R.string.cancel, mDisconnectListener)
-            .setOnCancelListener(mCancelListener)
-            .show();
-        ad.setCanceledOnTouchOutside(false);
-        // Making an assumption here that the dialog buttons have the ids starting
-        // with ...button1 as below
-        mPinButton1 = ad.findViewById(com.android.internal.R.id.button1);
-        if (mPinButton1 != null) {
-            mPinButton1.setEnabled(savedPin != null? savedPin.length() > 0 : false);
-        }
-        return ad;
-    }
-    
-    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
-        if (preference == mBTToggle) {
-            toggleBT();
-            return false;
-        } else if (preference == mBTVisibility) {
-            boolean vis = mBTVisibility.isChecked();
-            if (!vis) {
-                // Cancel discoverability
-                mBluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
-                mHandler.removeMessages(HANDLE_DISCOVERABLE_TIMEOUT);
-            } else {
-                mBluetooth.setMode(BluetoothDevice.MODE_DISCOVERABLE);
-                mBTVisibility.setSummaryOn(
-                        getResources().getString(R.string.bluetooth_is_discoverable,
-                                String.valueOf(mDiscoverableTime)));
-                mDiscoverableStartTime = SystemClock.elapsedRealtime();
-                mHandler.sendMessageDelayed(
-                        mHandler.obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT), 1000);
-            }
-        } else {
-            String key = preference.getKey();
-            if (key.startsWith(BT_KEY_PREFIX)) {
-                // Extract the device address from the key
-                String address = getAddressFromKey(key);
-                if (mBluetoothHeadset.isConnected(address)) {
-                    askDisconnect(address);
-                } else if (mBluetooth.hasBonding(address)) {
-                    if (mIsEnabled) {
-                        mBluetooth.cancelDiscovery();
-                    }
-                    updateRemoteDeviceStatus(address, STR_CONNECTING); 
-                    connect(preference, address);
-                } else {
-                    if (mIsEnabled) {
-                        mBluetooth.cancelDiscovery();
-                    }
-                    pair(preference, address);
-                }
-            }
-        }
-        return false;
-    }
-
-    /* Handle the key input to the PIN entry dialog */
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        
-        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER 
-                || keyCode == KeyEvent.KEYCODE_ENTER) {
-            String pin = ((EditText)v).getText().toString(); 
-            if (pin != null && pin.length() > 0) {
-                sendPin(pin);
-                mPinDialog.dismiss();
-                return true;
-            }
-        } else if (mPinButton1 != null) {
-            boolean valid =
-                    BluetoothDevice.convertPinToBytes(((EditText)v).getText().toString()) != null;
-            mPinButton1.setEnabled(valid);
-        }
-        return false;
-    }
-    
-    private void askDisconnect(String address) {
-        String name = mBluetooth.getRemoteName(address);
-        if (name == null) {
-            name = mRes.getString(R.string.bluetooth_device);
-        }
-        String message = mRes.getString(R.string.bluetooth_disconnect_blank, name);
-
-        mDisconnectAddress = address;
-        
-        AlertDialog ad = new AlertDialog.Builder(this)
-                .setTitle(message)
-                .setPositiveButton(android.R.string.ok, mDisconnectListener)
-                .setNegativeButton(android.R.string.cancel, null)
-                .show();
-        ad.setCanceledOnTouchOutside(false);
-
-    }
-
-    private void pairingDone(String address, boolean result) {
-        Preference pref = mDeviceMap.get(address);
-        if (pref != null) {
-            pref.setEnabled(true);
-            updateRemoteDeviceStatus(address);
-        } else if (result) {
-            // We've paired to a device that isn't in our list
-            addDeviceToUI(address, STR_PAIRED, mBluetooth.getRemoteName(address), 
-                    WEIGHT_PAIRED);
-        }
-    }
-    
-    private void pair(Preference pref, String address) {
-        pref.setEnabled(false);
-        pref.setSummary(STR_PAIRING);
-        mBluetooth.createBonding(address, sDeviceCallback);
-    }
-    
-    private void connect(Preference pref, String address) {
-        pref.setEnabled(false);
-        //TODO: Prompt the user to confirm they will disconnect current headset
-        disconnect();
-        mBluetoothHeadset.connectHeadset(address, mHeadsetCallback);
-    }
-
-    private void disconnect() {
-        int state = mBluetoothHeadset.getState();
-        if (state == BluetoothHeadset.STATE_CONNECTING ||
-                state == BluetoothHeadset.STATE_CONNECTED) {
-            mBluetoothHeadset.disconnectHeadset();
-        }
-    }
-    
-    private void toggleBT() {
-        if (mIsEnabled) {
-            mBTToggle.setSummaryOn(mRes.getString(R.string.bluetooth_stopping));
-            mBTDeviceList.setProgress(false);
-            // Force shutdown.
-            mBluetooth.cancelDiscovery();
-            mBluetooth.disable();
-        } else {
-            mBTToggle.setSummaryOff(mRes.getString(R.string.bluetooth_enabling));
-            mBTToggle.setChecked(false);
-            mBTToggle.setEnabled(false);
-            if (!mBluetooth.enable()) {
-                mBTToggle.setEnabled(true);
-            }
-        }
-    }
-
-    private void addDeviceToUI(String address, String summary, String name, 
-            int rssi) {
-        
-        if (address == null) {
-            return;
-        }
-
-        BluetoothListItem p;
-        if (mDeviceMap.containsKey(address)) {
-            p = (BluetoothListItem) mDeviceMap.get(address);
-            if (summary != null && summary.equals(STR_NOT_CONNECTED)) {
-                if (mBluetooth.hasBonding(address)) {
-                    summary = STR_PAIRED;
-                }
-            }
-            CharSequence oldSummary = p.getSummary();
-            if (oldSummary != null && oldSummary.equals(STR_CONNECTED)) {
-                summary = STR_CONNECTED; // Don't override connected with paired
-                mLastConnected = address;
-            }
-        } else {
-            p = new BluetoothListItem(this, null);
-        }
-        if (name == null) {
-            name = mBluetooth.getRemoteName(address);
-        }
-        if (name == null) {
-            name = address;
-        }
-
-        p.setTitle(name);
-        p.setSummary(summary);
-        p.setKey(BT_KEY_PREFIX + address);
-        // Enable the headset icon if it is most probably a headset class device
-        if (DeviceClass.getMajorClass(mBluetooth.getRemoteClass(address)) == 
-                DeviceClass.MAJOR_CLASS_AUDIO_VIDEO) {
-            p.setHeadset(true);
-        }
-        p.setWeight(rssi);
-        if (!mDeviceMap.containsKey(address)) {
-            mBTDeviceList.addPreference(p);
-            mDeviceMap.put(address, p);
-        }
-    }
-
-    private void addDevices(String [] addresses,
-            String[] deviceStatus, int[] rssi) {
-        for (int i = 0; i < addresses.length; i++) {
-            String status = deviceStatus[i];
-            String name = mBluetooth.getRemoteName(addresses[i]);
-            String address = addresses[i];
-            // Query the status if it's not known
-            if (status == null) {
-                if (mBluetoothHeadset.isConnected(addresses[i])) {
-                    status = STR_CONNECTED;
-                    mLastConnected = address;
-                } else if (mBluetooth.hasBonding(addresses[i])) {
-                    status = STR_PAIRED;
-                } else {
-                    status = STR_NOT_CONNECTED;
-                }
-            }
-            addDeviceToUI(address, status, name, rssi[i]);
-        }
-    }
-    
-    private void removeDeviceFromUI(String address) {
-        Preference p = mDeviceMap.get(address);
-        if (p == null) {
-            return;
-        }
-        mBTDeviceList.removePreference(p);
-        mDeviceMap.remove(address);
-    }
-    
-    private void updateDeviceName(String address, String name) {
-        Preference p = mDeviceMap.get(address);
-        if (p != null) {
-            p.setTitle(name);
-        }
-    }
-        
-    private void resetDeviceListUI() {
-        mDeviceMap.clear();
-
-        while (mBTDeviceList.getPreferenceCount() > 0) {
-            mBTDeviceList.removePreference(mBTDeviceList.getPreference(0));
-        }
-        if (!mIsEnabled) {
-            return;
-        }
-        
-        String connectedDevice = mBluetoothHeadset.getHeadsetAddress();
-        if (connectedDevice != null && mBluetoothHeadset.isConnected(connectedDevice)) {
-            addDeviceToUI(connectedDevice, STR_CONNECTED, 
-                    mBluetooth.getRemoteName(connectedDevice), WEIGHT_CONNECTED);
-        }
-        String [] bondedDevices = mBluetooth.listBondings();
-        if (bondedDevices != null) {
-            for (int i = 0; i < bondedDevices.length; i++) {
-                addDeviceToUI(bondedDevices[i], STR_PAIRED_NOT_NEARBY, 
-                        mBluetooth.getRemoteName(bondedDevices[i]), WEIGHT_PAIRED);
-            }
-        }
-    }
-    
-    private void unbond(String address) {
-        mBluetooth.removeBonding(address);
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
-            if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
-                mIsEnabled = true;
-                mBTToggle.setChecked(true);
-                mBTToggle.setSummaryOn(mRes.getString(R.string.bluetooth_enabled));
-                mBTToggle.setEnabled(true);
-                String name = mBluetooth.getName();
-                if (name != null) {
-                    mBTName.setSummary(name);
-                }
-                // save the "enabled" setting to database, so we can
-                // remember it on startup.
-                Settings.System.putInt(getContentResolver(),
-                                       Settings.System.BLUETOOTH_ON, 1);
-                resetDeviceListUI();
-                if (mAutoDiscovery) {
-                    mBluetooth.startDiscovery();
-                }
-            } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
-                mIsEnabled = false;
-                mBTToggle.setSummaryOff(mRes.getString(R.string.bluetooth_disabled));
-                resetDeviceListUI();
-                mBTVisibility.setChecked(false);
-                // save the "disabled" setting to database
-                Settings.System.putInt(getContentResolver(), 
-                                       Settings.System.BLUETOOTH_ON, 0);
-            } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)) {
-                if (address != null) {
-                    int rssi = intent.getShortExtra(BluetoothIntent.RSSI, 
-                            (short) WEIGHT_UNKNOWN);
-                    addDeviceToUI(address, STR_NOT_CONNECTED, null, rssi);
-                }
-            } else if (action.equals(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION)) {
-                String name = intent.getStringExtra(BluetoothIntent.NAME);
-                updateDeviceName(address, name);
-            } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION)) {
-                removeDeviceFromUI(address);
-            } else if (action.equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
-                mHandler.sendMessage(mHandler.obtainMessage(HANDLE_PIN_REQUEST, address));
-            } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
-                int state = intent.getIntExtra(BluetoothIntent.HEADSET_STATE,
-                                               BluetoothHeadset.STATE_ERROR);
-                if (state == BluetoothHeadset.STATE_CONNECTED) {
-                    mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
-                } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
-                    mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISCONNECTED, address));
-                } else if (state == BluetoothHeadset.STATE_CONNECTING) {
-                    mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTING, address));                    
-                }
-            } else if (action.equals(BluetoothIntent.DISCOVERY_STARTED_ACTION)) {
-                mBTDeviceList.setProgress(true);
-            } else if (action.equals(BluetoothIntent.DISCOVERY_COMPLETED_ACTION)) {
-                mBTDeviceList.setProgress(false);
-            } else if (action.equals(BluetoothIntent.MODE_CHANGED_ACTION)) {
-                mBTVisibility.setChecked(
-                        mBluetooth.getMode() == BluetoothDevice.MODE_DISCOVERABLE);
-            } else if (action.equals(BluetoothIntent.BONDING_CREATED_ACTION)) {
-                mHandler.sendMessage(mHandler.obtainMessage(HANDLE_PAIRING_PASSED, address));
-            } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION)) { 
-                mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
-            } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION)) { 
-                mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISCONNECTED, address));
-            }
-        }
-    };
-
-    
-    static class DeviceCallback extends IBluetoothDeviceCallback.Stub {
-        Handler messageHandler;
-
-        public void setHandler(Handler handler) {
-            synchronized (this) {
-                messageHandler = handler;
-            }
-        }
-        
-        public void onCreateBondingResult(String address, int result) {
-            synchronized (this) {
-                if (messageHandler != null) {
-                    if (result == BluetoothDevice.RESULT_FAILURE) {
-                        messageHandler.sendMessage(messageHandler.obtainMessage(
-                                HANDLE_PAIRING_FAILED, address));
-                    } else {
-                        messageHandler.sendMessage(messageHandler.obtainMessage(
-                                HANDLE_PAIRING_PASSED, address));
-                    }
-                }
-            }
-        }
-        
-        public void onEnableResult(int result) { }
-        public void onGetRemoteServiceChannelResult(String address, int channel) { }
-    };
-    
-    private IBluetoothHeadsetCallback mHeadsetCallback = new IBluetoothHeadsetCallback.Stub() {
-        public void onConnectHeadsetResult(String address, int resultCode) {
-            if (resultCode == BluetoothHeadset.RESULT_SUCCESS) {
-                mHandler.sendMessage(mHandler.obtainMessage(HANDLE_CONNECTED, address));
-            } else {
-                // Make toast in UI thread
-                mHandler.sendMessage(mHandler.obtainMessage(HANDLE_FAILED_TO_CONNECT, resultCode,
-                            -1, address));
-            }
-        }
-    };
-
-    private Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case HANDLE_CONNECTED:
-                case HANDLE_DISCONNECTED:
-                case HANDLE_CONNECTING:
-                    updateRemoteDeviceStatus((String) msg.obj);
-                    break;
-                case HANDLE_FAILED_TO_CONNECT:
-                    updateRemoteDeviceStatus((String) msg.obj);
-                    String name = mBluetooth.getRemoteName((String) msg.obj);
-                    if (name == null) {
-                        name = (String) msg.obj;
-                    }
-                    if (msg.arg1 == BluetoothHeadset.RESULT_FAILURE) {
-                        Toast.makeText(BluetoothSettings.this, 
-                                mRes.getString(R.string.failed_to_connect, name),
-                                Toast.LENGTH_SHORT).show();
-                    }
-                    break;
-                case HANDLE_PIN_REQUEST:
-                    mPinDialog = showPinDialog(null, (String) msg.obj);
-                    break;
-                case HANDLE_DISCOVERABLE_TIMEOUT:
-                    long nowTime = SystemClock.elapsedRealtime();
-                    int secondsLeft = mDiscoverableTime
-                            - (int) (nowTime - mDiscoverableStartTime) / 1000;
-                    if (secondsLeft > 0) {
-                        mBTVisibility.setSummaryOn(
-                                getResources().getString(R.string.bluetooth_is_discoverable,
-                                        String.valueOf(secondsLeft)));
-                        sendMessageDelayed(obtainMessage(HANDLE_DISCOVERABLE_TIMEOUT), 1000);
-                    } else {
-                        mBluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
-                        mBTVisibility.setChecked(false);
-                    }
-                    break;
-                case HANDLE_INITIAL_SCAN:
-                    if (mBluetoothHeadset.getState() == BluetoothHeadset.STATE_ERROR &&
-                            ((Integer)msg.obj).intValue() < 2) {
-                        // Second attempt after another 100ms
-                        sendMessageDelayed(obtainMessage(HANDLE_INITIAL_SCAN, 2), 100);
-                    } else {
-                        resetDeviceListUI();
-                        if (mAutoDiscovery) {
-                            mBluetooth.cancelDiscovery();
-                            mBluetooth.startDiscovery();
-                        }
-                    }
-                    break;
-                case HANDLE_PAIRING_PASSED:
-                    String addr = (String) msg.obj;
-                    pairingDone(addr, true);
-                    break;
-                case HANDLE_PAIRING_FAILED:
-                    String address = (String) msg.obj;
-                    pairingDone(address, false);
-                    String pairName = mBluetooth.getRemoteName(address);
-                    if (pairName == null) {
-                        pairName = address;
-                    }
-                    Toast.makeText(BluetoothSettings.this, 
-                            mRes.getString(R.string.failed_to_pair, pairName),
-                            Toast.LENGTH_SHORT).show();
-                    break;
-                case HANDLE_PAUSE_TIMEOUT:
-                    // Possibility of race condition, but not really harmful
-                    if (!sIsRunning) {
-                        Object[] params = (Object[]) msg.obj;
-                        BluetoothDevice bluetooth = (BluetoothDevice) params[0];
-                        if (bluetooth.isEnabled()) {
-                            if (bluetooth.isDiscovering()) {
-                                bluetooth.cancelDiscovery();
-                            }
-                            if (params[1] != null) {
-                                bluetooth.cancelBondingProcess((String) params[1]);
-                            }
-                            bluetooth.setMode(BluetoothDevice.MODE_CONNECTABLE);
-                        }
-                    }
-                    break;
-            }
-        }
-    };
-
-    private DialogInterface.OnClickListener mDisconnectListener = 
-        new DialogInterface.OnClickListener() {
-        
-            public void onClick(DialogInterface dialog, int which) {
-                if (dialog == mPinDialog) {
-                    if (which == DialogInterface.BUTTON1) {
-                        String pin = mPinEdit.getText().toString();
-                        if (pin != null && pin.length() > 0) {
-                            sendPin(pin);
-                        } else {
-                            sendPin(null);
-                        }
-                    } else {
-                        sendPin(null);
-                    }
-                    mPinDialog = null;
-                    mPinEdit = null;
-                } else {
-                    if (which == DialogInterface.BUTTON1) {
-                        disconnect();
-                    }
-                }
-            }
-    };
-
-    private DialogInterface.OnCancelListener mCancelListener =
-        new DialogInterface.OnCancelListener() {
-            public void onCancel(DialogInterface dialog) {
-                if (dialog == mPinDialog) {
-                    sendPin(null);
-                }
-                mPinDialog = null;
-                mPinEdit = null;
-            }
-    };
-}
-
diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java
index 973e655..3097e96 100644
--- a/src/com/android/settings/ChooseLockPattern.java
+++ b/src/com/android/settings/ChooseLockPattern.java
@@ -45,6 +45,17 @@
  */
 public class ChooseLockPattern extends Activity implements View.OnClickListener{
 
+    /**
+     * Used by the choose lock pattern wizard to indicate the wizard is
+     * finished, and each activity in the wizard should finish.
+     * <p>
+     * Previously, each activity in the wizard would finish itself after
+     * starting the next activity. However, this leads to broken 'Back'
+     * behavior. So, now an activity does not finish itself until it gets this
+     * result.
+     */
+    static final int RESULT_FINISHED = RESULT_FIRST_USER;
+    
     // how long after a confirmation message is shown before moving on
     static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
 
@@ -298,6 +309,8 @@
                 mLockPatternView.clearPattern();
                 updateStage(Stage.Introduction);
             } else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
+                // They are canceling the entire wizard
+                setResult(RESULT_FINISHED);
                 finish();
             } else {
                 throw new IllegalStateException("left footer button pressed, but stage of " +
@@ -368,6 +381,7 @@
         }
 
         if (resultCode != Activity.RESULT_OK) {
+            setResult(RESULT_FINISHED);
             finish();
         }
         updateStage(Stage.Introduction);
@@ -475,6 +489,8 @@
             mLockPatternUtils.setLockPatternEnabled(true);
             mLockPatternUtils.setVisiblePatternEnabled(true);
         }
+        
+        setResult(RESULT_FINISHED);
         finish();
     }
 }
diff --git a/src/com/android/settings/ChooseLockPatternExample.java b/src/com/android/settings/ChooseLockPatternExample.java
index 5feba4c..77517b9 100644
--- a/src/com/android/settings/ChooseLockPatternExample.java
+++ b/src/com/android/settings/ChooseLockPatternExample.java
@@ -25,6 +25,7 @@
 import android.widget.ImageView;
 
 public class ChooseLockPatternExample extends Activity implements View.OnClickListener {
+    private static final int REQUESTCODE_CHOOSE = 1;
     private static final long START_DELAY = 1000;
     protected static final String TAG = "Settings";
     private View mNextButton;
@@ -59,15 +60,24 @@
     
     public void onClick(View v) {
         if (v == mSkipButton) {
+            // Canceling, so finish all
+            setResult(ChooseLockPattern.RESULT_FINISHED);
             finish();
         } else if (v == mNextButton) {
             stopAnimation(mAnimation);
             Intent intent = new Intent(this, ChooseLockPattern.class);
-            startActivity(intent);
+            startActivityForResult(intent, REQUESTCODE_CHOOSE);
+        }
+    }
+    
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUESTCODE_CHOOSE && resultCode == ChooseLockPattern.RESULT_FINISHED) {
+            setResult(resultCode);
             finish();
         }
     }
-
+    
     private void initViews() {
         mNextButton = findViewById(R.id.next_button);
         mNextButton.setOnClickListener(this);
diff --git a/src/com/android/settings/ChooseLockPatternTutorial.java b/src/com/android/settings/ChooseLockPatternTutorial.java
index 9687b55..a0a878a 100644
--- a/src/com/android/settings/ChooseLockPatternTutorial.java
+++ b/src/com/android/settings/ChooseLockPatternTutorial.java
@@ -24,8 +24,10 @@
 import android.view.View;
 
 public class ChooseLockPatternTutorial extends Activity implements View.OnClickListener {
-    protected View mNextButton;
-    protected View mSkipButton;
+    private static final int REQUESTCODE_EXAMPLE = 1;
+    
+    private View mNextButton;
+    private View mSkipButton;
     
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -52,11 +54,22 @@
 
     public void onClick(View v) {
         if (v == mSkipButton) {
+            // Canceling, so finish all
+            setResult(ChooseLockPattern.RESULT_FINISHED);
             finish();
         } else if (v == mNextButton) {
-            startActivity(new Intent(this, ChooseLockPatternExample.class));
+            startActivityForResult(new Intent(this, ChooseLockPatternExample.class),
+                    REQUESTCODE_EXAMPLE);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUESTCODE_EXAMPLE && resultCode == ChooseLockPattern.RESULT_FINISHED) {
+            setResult(resultCode);
             finish();
         }
     }
+    
 }
 
diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java
index ce9dd8c..ead38d1 100644
--- a/src/com/android/settings/DateTimeSettings.java
+++ b/src/com/android/settings/DateTimeSettings.java
@@ -27,7 +27,6 @@
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.pim.DateFormat;
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
@@ -35,6 +34,7 @@
 import android.preference.PreferenceScreen;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.text.format.DateFormat;
 import android.widget.DatePicker;
 import android.widget.TimePicker;
 
@@ -281,9 +281,7 @@
     /*  Get & Set values from the system settings  */
     
     private boolean is24Hour() {
-        String setting = Settings.System.getString(getContentResolver(),
-                Settings.System.TIME_12_24);
-        return HOURS_24.equals(setting);
+        return DateFormat.is24HourFormat(this);
     }
     
     private void set24Hour(boolean is24Hour) {
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 02b852b..155f085 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.settings;
 
+import android.os.BatteryManager;
 import android.os.Bundle;
 import android.os.SystemProperties;
 import android.preference.Preference;
@@ -32,9 +33,11 @@
 
     private static final String ENABLE_ADB = "enable_adb";
     private static final String KEEP_SCREEN_ON = "keep_screen_on";
+    private static final String ALLOW_MOCK_LOCATION = "allow_mock_location";
 
     private CheckBoxPreference mEnableAdb;
     private CheckBoxPreference mKeepScreenOn;
+    private CheckBoxPreference mAllowMockLocation;
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -44,16 +47,19 @@
 
         mEnableAdb = (CheckBoxPreference) findPreference(ENABLE_ADB);
         mKeepScreenOn = (CheckBoxPreference) findPreference(KEEP_SCREEN_ON);
+        mAllowMockLocation = (CheckBoxPreference) findPreference(ALLOW_MOCK_LOCATION);
     }
 
     @Override
     protected void onResume() {
         super.onResume();
         
-        mEnableAdb.setChecked(Settings.System.getInt(getContentResolver(),
-                Settings.System.ADB_ENABLED, 0) != 0);
+        mEnableAdb.setChecked(Settings.Secure.getInt(getContentResolver(),
+                Settings.Secure.ADB_ENABLED, 0) != 0);
         mKeepScreenOn.setChecked(Settings.System.getInt(getContentResolver(),
                 Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0) != 0);
+        mAllowMockLocation.setChecked(Settings.Secure.getInt(getContentResolver(),
+                Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0);
     }
 
     @Override
@@ -66,11 +72,15 @@
         }
 
         if (preference == mEnableAdb) {
-            Settings.System.putInt(getContentResolver(), Settings.System.ADB_ENABLED, 
+            Settings.Secure.putInt(getContentResolver(), Settings.Secure.ADB_ENABLED, 
                     mEnableAdb.isChecked() ? 1 : 0);
         } else if (preference == mKeepScreenOn) {
             Settings.System.putInt(getContentResolver(), Settings.System.STAY_ON_WHILE_PLUGGED_IN, 
-                    mKeepScreenOn.isChecked() ? 1 : 0);
+                    mKeepScreenOn.isChecked() ? 
+                    (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB) : 0);
+        } else if (preference == mAllowMockLocation) {
+            Settings.Secure.putInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION,
+                    mAllowMockLocation.isChecked() ? 1 : 0);
         }
         
         return false;
diff --git a/src/com/android/settings/DeviceInfoSettings.java b/src/com/android/settings/DeviceInfoSettings.java
index 58fc91e..5d72afc 100644
--- a/src/com/android/settings/DeviceInfoSettings.java
+++ b/src/com/android/settings/DeviceInfoSettings.java
@@ -46,6 +46,7 @@
     private static final String KEY_TERMS = "terms";
     private static final String KEY_LICENSE = "license";
     private static final String KEY_COPYRIGHT = "copyright";
+    private static final String KEY_SYSTEM_UPDATE_SETTINGS = "system_update_settings";
     
     @Override
     protected void onCreate(Bundle icicle) {
@@ -56,7 +57,7 @@
         setSummary("firmware_version", "ro.build.version.release");
         setSummary("baseband_version", "gsm.version.baseband");
         setSummary("device_model", "ro.product.model");
-        setSummary("build_number", "ro.build.description");
+        setSummary("build_number", "ro.build.version.incremental");
         findPreference("kernel_version").setSummary(getFormattedKernelVersion());
 
         /*
@@ -74,6 +75,8 @@
                 Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
         Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_TEAM,
                 Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
+        Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_SYSTEM_UPDATE_SETTINGS,
+                Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
     }
     
     private void setSummary(String preference, String property) {
diff --git a/src/com/android/settings/InputMethodsSettings.java b/src/com/android/settings/InputMethodsSettings.java
new file mode 100644
index 0000000..d38779d
--- /dev/null
+++ b/src/com/android/settings/InputMethodsSettings.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2008 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.settings;
+
+import java.util.HashSet;
+import java.util.List;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.preference.CheckBoxPreference;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+/*
+ * Displays preferences for input methods.
+ */
+public class InputMethodsSettings extends PreferenceActivity {
+    private List<InputMethodInfo> mInputMethodProperties;
+
+    final TextUtils.SimpleStringSplitter mStringColonSplitter
+            = new TextUtils.SimpleStringSplitter(':');
+    
+    private String mLastInputMethodId;
+    private String mLastTickedInputMethodId;
+
+    static public String getInputMethodIdFromKey(String key) {
+        return key;
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.input_methods_prefs);
+
+        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+
+        mInputMethodProperties = imm.getInputMethodList();
+
+        mLastInputMethodId = Settings.Secure.getString(getContentResolver(),
+            Settings.Secure.DEFAULT_INPUT_METHOD);
+
+        int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties
+                .size());
+        for (int i = 0; i < N; ++i) {
+            InputMethodInfo property = mInputMethodProperties.get(i);
+            String prefKey = property.getId();
+
+            CharSequence label = property.loadLabel(getPackageManager());
+            
+            // Add a check box.
+            CheckBoxPreference chkbxPref = new CheckBoxPreference(this);
+            chkbxPref.setKey(prefKey);
+            chkbxPref.setTitle(label);
+            getPreferenceScreen().addPreference(chkbxPref);
+
+            // If setting activity is available, add a setting screen entry.
+            if (null != property.getSettingsActivity()) {
+                PreferenceScreen prefScreen = new PreferenceScreen(this, null);
+                prefScreen.setKey(property.getSettingsActivity());
+                // XXX TODO: handle localization properly.
+                prefScreen.setTitle(label + " settings");
+                getPreferenceScreen().addPreference(prefScreen);
+            }
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        final HashSet<String> enabled = new HashSet<String>();
+        String enabledStr = Settings.Secure.getString(getContentResolver(),
+                Settings.Secure.ENABLED_INPUT_METHODS);
+        if (enabledStr != null) {
+            final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+            splitter.setString(enabledStr);
+            while (splitter.hasNext()) {
+                enabled.add(splitter.next());
+            }
+        }
+        
+        // Update the statuses of the Check Boxes.
+        int N = mInputMethodProperties.size();
+        for (int i = 0; i < N; ++i) {
+            final String id = mInputMethodProperties.get(i).getId();
+            CheckBoxPreference pref = (CheckBoxPreference) findPreference(mInputMethodProperties
+                    .get(i).getId());
+            pref.setChecked(enabled.contains(id));
+        }
+        mLastTickedInputMethodId = null;
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        StringBuilder builder = new StringBuilder(256);
+        
+        boolean haveLastInputMethod = false;
+        
+        int firstEnabled = -1;
+        int N = mInputMethodProperties.size();
+        for (int i = 0; i < N; ++i) {
+            final String id = mInputMethodProperties.get(i).getId();
+            CheckBoxPreference pref = (CheckBoxPreference) findPreference(id);
+            boolean hasIt = id.equals(mLastInputMethodId);
+            if (pref.isChecked()) {
+                if (builder.length() > 0) builder.append(':');
+                builder.append(id);
+                if (firstEnabled < 0) {
+                    firstEnabled = i;
+                }
+                if (hasIt) haveLastInputMethod = true;
+            } else if (hasIt) {
+                mLastInputMethodId = mLastTickedInputMethodId;
+            }
+        }
+
+        // If the last input method is unset, set it as the first enabled one.
+        if (null == mLastInputMethodId || "".equals(mLastInputMethodId)) {
+            if (firstEnabled >= 0) {
+                mLastInputMethodId = mInputMethodProperties.get(firstEnabled).getId();
+            } else {
+                mLastInputMethodId = null;
+            }
+        }
+        
+        Settings.Secure.putString(getContentResolver(),
+            Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
+        Settings.Secure.putString(getContentResolver(),
+            Settings.Secure.DEFAULT_INPUT_METHOD, mLastInputMethodId);
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+            Preference preference) {
+
+        // Those monkeys kept committing suicide, so we add this property
+        // to disable this functionality
+        if (!TextUtils.isEmpty(SystemProperties.get("ro.monkey"))) {
+            return false;
+        }
+
+        if (preference instanceof CheckBoxPreference) {
+            CheckBoxPreference chkPref = (CheckBoxPreference) preference;
+            String id = getInputMethodIdFromKey(chkPref.getKey());
+            if (chkPref.isChecked()) {
+                mLastTickedInputMethodId = id;
+            } else if (id.equals(mLastTickedInputMethodId)) {
+                mLastTickedInputMethodId = null;
+            }
+        } else if (preference instanceof PreferenceScreen) {
+            if (preference.getIntent() == null) {
+                PreferenceScreen pref = (PreferenceScreen) preference;
+                String activityName = pref.getKey();
+                String packageName = activityName.substring(0, activityName
+                        .lastIndexOf("."));
+                if (activityName.length() > 0) {
+                    Intent i = new Intent(Intent.ACTION_MAIN);
+                    i.setClassName(packageName, activityName);
+                    startActivity(i);
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/src/com/android/settings/InstalledAppDetails.java b/src/com/android/settings/InstalledAppDetails.java
index 712f94d..d3e7344 100644
--- a/src/com/android/settings/InstalledAppDetails.java
+++ b/src/com/android/settings/InstalledAppDetails.java
@@ -29,7 +29,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageStatsObserver;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageStats;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -37,6 +36,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.text.format.Formatter;
 import android.util.Config;
 import android.util.Log;
 import java.util.ArrayList;
@@ -50,33 +50,62 @@
 import android.widget.TextView;
 
 /**
- * Activity to display application information from Settings
- * 
+ * Activity to display application information from Settings. This activity presents
+ * extended information associated with a package like code, data, total size, permissions
+ * used by the application and also the set of default launchable activities.
+ * For system applications, an option to clear user data is displayed only if data size is > 0.
+ * System applications that do not want clear user data do not have this option.
+ * For non-system applications, there is no option to clear data. Instead there is an option to
+ * uninstall the application.
  */
 public class InstalledAppDetails extends Activity implements View.OnClickListener, DialogInterface.OnClickListener  {
     private static final String TAG="InstalledAppDetails";
     private static final int _UNKNOWN_APP=R.string.unknown;
-   //wait times used for the async package manager api
     private ApplicationInfo mAppInfo;
-    private Button mUninstallButton;
+    private Button mAppButton;
     private Button mActivitiesButton;
-    private boolean mSysPackage;
-    private boolean localLOGV=Config.LOGV || true;
+    private boolean mCanUninstall;
+    private boolean localLOGV=Config.LOGV || false;
     private TextView mTotalSize;
     private TextView mAppSize;
     private TextView mDataSize;
+    private PkgSizeObserver mSizeObserver;
+    private ClearUserDataObserver mClearDataObserver;
+    // Views related to cache info
+    private View mCachePanel;
+    private TextView mCacheSize;
+    private Button mClearCacheButton;
+    private ClearCacheObserver mClearCacheObserver;
+    
     PackageStats mSizeInfo;
     private Button mManageSpaceButton;
     private PackageManager mPm;
-    private String mBStr, mKbStr, mMbStr;
     
     //internal constants used in Handler
-    private static final int CLEAR_USER_DATA = 1;
     private static final int OP_SUCCESSFUL = 1;
     private static final int OP_FAILED = 2;
+    private static final int CLEAR_USER_DATA = 1;
     private static final int GET_PKG_SIZE = 2;
+    private static final int CLEAR_CACHE = 3;
     private static final String ATTR_PACKAGE_STATS="PackageStats";
     
+    // invalid size value used initially and also when size retrieval through PackageManager
+    // fails for whatever reason
+    private static final int SIZE_INVALID = -1;
+    
+    // Resource strings
+    private CharSequence mInvalidSizeStr;
+    private CharSequence mComputingStr;
+    private CharSequence mAppButtonText;
+    
+    // Possible btn states
+    private enum AppButtonStates {
+        CLEAR_DATA,
+        UNINSTALL,
+        NONE
+    } 
+    private AppButtonStates mAppButtonState;
+    
     private Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -86,17 +115,22 @@
                 case GET_PKG_SIZE:
                     refreshSizeInfo(msg);
                     break;
+                case CLEAR_CACHE:
+                    // Refresh size info
+                    mPm.getPackageSizeInfo(mAppInfo.packageName, mSizeObserver);
+                    break;
                 default:
                     break;
             }
         }
     };
     
-    private boolean isSystemPackage() {
-        if ((mAppInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
-            return true;
+    private boolean isUninstallable() {
+        if (((mAppInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) && 
+                ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0)) {
+            return false;
         }
-        return false;
+        return true;
     }
     
     class ClearUserDataObserver extends IPackageDataObserver.Stub {
@@ -119,28 +153,46 @@
          }
      }
     
+    class ClearCacheObserver extends IPackageDataObserver.Stub {
+        public void onRemoveCompleted(final String packageName, final boolean succeeded) {
+            final Message msg = mHandler.obtainMessage(CLEAR_CACHE);
+            msg.arg1 = succeeded?OP_SUCCESSFUL:OP_FAILED;
+            mHandler.sendMessage(msg);
+         }
+     }
+    
     private String getSizeStr(long size) {
-        String retStr = "";
-        if(size < 1024) {
-            return String.valueOf(size)+mBStr;
+        if (size == SIZE_INVALID) {
+            return mInvalidSizeStr.toString();
         }
-        long kb, mb, rem;
-        kb = size >> 10;
-        rem = size - (kb << 10);
-        if(kb < 1024) {
-            if(rem > 512) {
-                kb++;
+        return Formatter.formatFileSize(this, size);
+    }
+    
+    private void setAppBtnState() {
+        boolean visible = false;
+        if(mCanUninstall) {
+            //app can clear user data
+            if((mAppInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) 
+                    == ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) {
+                mAppButtonText = getText(R.string.clear_user_data_text);
+               mAppButtonState = AppButtonStates.CLEAR_DATA;
+               visible = true;
+            } else {
+                //hide button if diableClearUserData is set
+                visible = false;
+                mAppButtonState = AppButtonStates.NONE;
             }
-            retStr += String.valueOf(kb)+mKbStr;
-            return retStr;
+        } else {
+            visible = true;
+            mAppButtonState = AppButtonStates.UNINSTALL;
+            mAppButtonText = getText(R.string.uninstall_text);
         }
-        mb = kb >> 10;
-        if(kb >= 512) {
-            //round off
-            mb++;
-       }
-       retStr += String.valueOf(mb)+ mMbStr;
-       return retStr;
+        if(visible) {
+            mAppButton.setText(mAppButtonText);
+            mAppButton.setVisibility(View.VISIBLE);
+        } else {
+            mAppButton.setVisibility(View.GONE);
+        }
     }
     
     /** Called when the activity is first created. */
@@ -152,45 +204,25 @@
         //get application's name from intent
         Intent intent = getIntent();
         final String packageName = intent.getStringExtra(ManageApplications.APP_PKG_NAME);
-        mSizeInfo = intent.getParcelableExtra(ManageApplications.APP_PKG_SIZE);
-        long total = -1;
-        long code = -1;
-        long data = -1;
-        if(mSizeInfo != null) {
-            total = mSizeInfo.cacheSize+mSizeInfo.codeSize+mSizeInfo.dataSize;
-            code = mSizeInfo.codeSize;
-            data = mSizeInfo.dataSize+mSizeInfo.cacheSize;
-        }
-        String unknownStr = getString(_UNKNOWN_APP);
-        mBStr = getString(R.string.b_text);
-        mKbStr = getString(R.string.kb_text);
-        mMbStr = getString(R.string.mb_text);
-        String totalSizeStr = unknownStr;
-        if(total != -1) {
-            totalSizeStr = getSizeStr(total);
-        }
-        String appSizeStr = unknownStr;
-        if(code != -1) {
-            appSizeStr = getSizeStr(code);
-        }
-        String dataSizeStr = unknownStr;
-        if(data != -1) {
-            dataSizeStr = getSizeStr(data);
-        }
-        if(localLOGV) Log.i(TAG, "packageName:"+packageName+", total="+total+
-                "code="+code+", data="+data);
+        mComputingStr = getText(R.string.computing_size);
+        // Try retrieving package stats again
+        CharSequence totalSizeStr, appSizeStr, dataSizeStr;
+        totalSizeStr = appSizeStr = dataSizeStr = mComputingStr;
+        if(localLOGV) Log.i(TAG, "Have to compute package sizes");
+        mSizeObserver = new PkgSizeObserver();
+        mPm.getPackageSizeInfo(packageName, mSizeObserver);
+
         try {
-            mAppInfo = mPm.getApplicationInfo(packageName, 0);
+            mAppInfo = mPm.getApplicationInfo(packageName, 
+                    PackageManager.GET_UNINSTALLED_PACKAGES);
         } catch (NameNotFoundException e) {
-           Throwable th = e.fillInStackTrace();
             Log.e(TAG, "Exception when retrieving package:"+packageName, e);
             displayErrorDialog(R.string.app_not_found_dlg_text, true, true);
         }
-        setContentView(R.layout.installed_app_details);
-        ((ImageView)findViewById(R.id.app_icon)).setImageDrawable(mPm.
-                getApplicationIcon(mAppInfo));
+        setContentView(R.layout.installed_app_details);       
+        ((ImageView)findViewById(R.id.app_icon)).setImageDrawable(mAppInfo.loadIcon(mPm));
         //set application name TODO version
-        CharSequence appName = mPm.getApplicationLabel(mAppInfo);
+        CharSequence appName = mAppInfo.loadLabel(mPm);
         if(appName == null) {
             appName = getString(_UNKNOWN_APP);
         }
@@ -208,33 +240,22 @@
         mDataSize = (TextView)findViewById(R.id.data_size_text);
         mDataSize.setText(dataSizeStr);
          
-         mUninstallButton = ((Button)findViewById(R.id.uninstall_button));
+         mAppButton = ((Button)findViewById(R.id.uninstall_button));
         //determine if app is a system app
-         mSysPackage = isSystemPackage();
-         if(localLOGV) Log.i(TAG, "Is systemPackage "+mSysPackage);
-         int btnText;
-         boolean btnClickable = true;
-         
-         if(mSysPackage) {
-             //app can clear user data
-             if((mAppInfo.flags & ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) 
-                     == ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA) {
-                 mUninstallButton.setText(R.string.clear_user_data_text);
-                 //disable button if data is 0
-                 if(data == 0) {
-                     mUninstallButton.setEnabled(false);
-                 } else {
-                     //enable button
-                     mUninstallButton.setOnClickListener(this);
-                 }
-             } else {
-                 //hide button if diableClearUserData is set
-                 mUninstallButton.setVisibility(View.GONE);
-             }
-         } else {
-             mUninstallButton.setText(R.string.uninstall_text);
-             mUninstallButton.setOnClickListener(this);
+         mCanUninstall = !isUninstallable();
+         if(localLOGV) Log.i(TAG, "Is systemPackage "+mCanUninstall);
+         setAppBtnState();
+         mManageSpaceButton = (Button)findViewById(R.id.manage_space_button);
+         if(mAppInfo.manageSpaceActivityName != null) {
+             mManageSpaceButton.setVisibility(View.VISIBLE);
+             mManageSpaceButton.setOnClickListener(this);
          }
+         
+         // Cache section
+         mCachePanel = findViewById(R.id.cache_panel);
+         mCacheSize = (TextView) findViewById(R.id.cache_size_text);
+         mClearCacheButton = (Button) findViewById(R.id.clear_cache_button);
+         
          //clear activities
          mActivitiesButton = (Button)findViewById(R.id.clear_activities_button);
          List<ComponentName> prefActList = new ArrayList<ComponentName>();
@@ -251,23 +272,19 @@
              autoLaunchView.setText(R.string.auto_launch_enable_text);
              mActivitiesButton.setOnClickListener(this);
          }
-         mManageSpaceButton = (Button)findViewById(R.id.manage_space_button);
-         if(mAppInfo.manageSpaceActivityName != null) {
-             mManageSpaceButton.setVisibility(View.VISIBLE);
-             mManageSpaceButton.setOnClickListener(this);
+         
+         // security permissions section
+         LinearLayout permsView = (LinearLayout) findViewById(R.id.permissions_section);
+         AppSecurityPermissions asp = new AppSecurityPermissions(this, packageName);
+         if(asp.getPermissionCount() > 0) {
+             permsView.setVisibility(View.VISIBLE);
+             // Make the security sections header visible
+             LinearLayout securityList = (LinearLayout) permsView.findViewById(
+                     R.id.security_settings_list);
+             securityList.addView(asp.getPermissionsView());
+         } else {
+             permsView.setVisibility(View.GONE);
          }
-         //security permissions section
-         AppSecurityPermissions asp = new AppSecurityPermissions(this);
-         PackageInfo pkgInfo;
-        try {
-            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
-        } catch (NameNotFoundException e) {
-            Log.w(TAG, "Couldnt retrieve permissions for package:"+packageName);
-            return;
-        }
-         asp.setSecurityPermissionsView(pkgInfo);
-         LinearLayout securityList = (LinearLayout) findViewById(R.id.security_settings_list);
-         securityList.addView(asp.getPermissionsView());
     }
     
     private void displayErrorDialog(int msgId, final boolean finish, final boolean changed) {
@@ -292,7 +309,7 @@
         Intent intent = new Intent();
         intent.putExtra(ManageApplications.APP_CHG, appChanged);
         setResult(ManageApplications.RESULT_OK, intent);
-        mUninstallButton.setEnabled(false);
+        mAppButton.setEnabled(false);
         if(finish) {
             finish();
         }
@@ -305,29 +322,53 @@
      */
     private void refreshSizeInfo(Message msg) {
         boolean changed = false;
-        Intent intent = new Intent();
         PackageStats newPs = msg.getData().getParcelable(ATTR_PACKAGE_STATS);
         long newTot = newPs.cacheSize+newPs.codeSize+newPs.dataSize;
-        long oldTot = mSizeInfo.cacheSize+mSizeInfo.codeSize+mSizeInfo.dataSize;
-        if(newTot != oldTot) {
-            mTotalSize.setText(getSizeStr(newTot));
-            changed = true;
-        }
-        if(newPs.codeSize != mSizeInfo.codeSize) {
-            mAppSize.setText(getSizeStr(newPs.codeSize));
-            changed = true;
-        }
-        if((newPs.dataSize != mSizeInfo.dataSize) || (newPs.cacheSize != mSizeInfo.cacheSize)) {
-            mDataSize.setText(getSizeStr(newPs.dataSize+newPs.cacheSize));
-            changed = true;
-        }
-        if(changed) {
-            mUninstallButton.setText(R.string.clear_user_data_text);
+        if(mSizeInfo == null) {
             mSizeInfo = newPs;
-            intent.putExtra(ManageApplications.APP_PKG_SIZE, mSizeInfo);
+            mTotalSize.setText(getSizeStr(newTot));
+            mAppSize.setText(getSizeStr(newPs.codeSize));
+            mDataSize.setText(getSizeStr(newPs.dataSize+newPs.cacheSize));
+        } else {
+            long oldTot = mSizeInfo.cacheSize+mSizeInfo.codeSize+mSizeInfo.dataSize;
+            if(newTot != oldTot) {
+                mTotalSize.setText(getSizeStr(newTot));
+                changed = true;
+            }
+            if(newPs.codeSize != mSizeInfo.codeSize) {
+                mAppSize.setText(getSizeStr(newPs.codeSize));
+                changed = true;
+            }
+            if((newPs.dataSize != mSizeInfo.dataSize) || (newPs.cacheSize != mSizeInfo.cacheSize)) {
+                mDataSize.setText(getSizeStr(newPs.dataSize+newPs.cacheSize));
+            }
+            if(changed) {
+                mSizeInfo = newPs;
+            }
         }
-        intent.putExtra(ManageApplications.APP_CHG, changed);
-        setResult(ManageApplications.RESULT_OK, intent);
+        
+        long data = mSizeInfo.dataSize+mSizeInfo.cacheSize;
+        // Disable button if data is 0
+        if(mAppButtonState != AppButtonStates.NONE){
+            mAppButton.setText(mAppButtonText);
+            if((mAppButtonState == AppButtonStates.CLEAR_DATA) && (data == 0)) {
+                mAppButton.setEnabled(false);
+            } else {
+                mAppButton.setEnabled(true);
+                mAppButton.setOnClickListener(this);
+            }            
+        }
+        refreshCacheInfo(newPs.cacheSize);
+    }
+    
+    private void refreshCacheInfo(long cacheSize) {
+        // Set cache info
+        mCacheSize.setText(getSizeStr(cacheSize));
+        if (cacheSize <= 0) {
+            mClearCacheButton.setEnabled(false);
+        } else {
+            mClearCacheButton.setOnClickListener(this);
+        }
     }
     
     /*
@@ -339,11 +380,10 @@
         String packageName = mAppInfo.packageName;
         if(result == OP_SUCCESSFUL) {
             Log.i(TAG, "Cleared user data for system package:"+packageName);
-            PkgSizeObserver observer = new PkgSizeObserver();
-            mPm.getPackageSizeInfo(packageName, observer);
+            mPm.getPackageSizeInfo(packageName, mSizeObserver);
         } else {
-            mUninstallButton.setText(R.string.clear_user_data_text);
-            mUninstallButton.setEnabled(true);
+            mAppButton.setText(R.string.clear_user_data_text);
+            mAppButton.setEnabled(true);
         }
     }
     
@@ -352,20 +392,21 @@
      * button for a system package
      */
     private  void initiateClearUserDataForSysPkg() {
-        mUninstallButton.setEnabled(false);
+        mAppButton.setEnabled(false);
         //invoke uninstall or clear user data based on sysPackage
-        boolean recomputeSizes = false;
         String packageName = mAppInfo.packageName;
         Log.i(TAG, "Clearing user data for system package");
-        ClearUserDataObserver observer = new ClearUserDataObserver();
+        if(mClearDataObserver == null) {
+            mClearDataObserver = new ClearUserDataObserver();
+        }
         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
-        boolean res = am.clearApplicationUserData(packageName, observer);
+        boolean res = am.clearApplicationUserData(packageName, mClearDataObserver);
         if(!res) {
             //doesnt initiate clear. some error. should not happen but just log error for now
             Log.i(TAG, "Couldnt clear application user data for package:"+packageName);
             displayErrorDialog(R.string.clear_data_failed, false, false);
         } else {
-                mUninstallButton.setText(R.string.recompute_size);
+                mAppButton.setText(R.string.recompute_size);
         }
     }
     
@@ -375,8 +416,8 @@
      */
     public void onClick(View v) {
         String packageName = mAppInfo.packageName;
-        if(v == mUninstallButton) {
-            if(mSysPackage) {
+        if(v == mAppButton) {
+            if(mCanUninstall) {
                 //display confirmation dialog
                 new AlertDialog.Builder(this)
                 .setTitle(getString(R.string.clear_data_dlg_title))
@@ -399,11 +440,17 @@
             Intent intent = new Intent(Intent.ACTION_DEFAULT);
             intent.setClassName(mAppInfo.packageName, mAppInfo.manageSpaceActivityName);
             startActivityForResult(intent, -1);
+        } else if (v == mClearCacheButton) {
+            // Lazy initialization of observer
+            if (mClearCacheObserver == null) {
+                mClearCacheObserver = new ClearCacheObserver();
+            }
+            mPm.deleteApplicationCacheFiles(packageName, mClearCacheObserver);
         }
     }
 
     public void onClick(DialogInterface dialog, int which) {
-        if(which == AlertDialog.BUTTON1) {
+        if(which == AlertDialog.BUTTON_POSITIVE) {
             //invoke uninstall or clear user data based on sysPackage
             initiateClearUserDataForSysPkg();
         } else {
diff --git a/src/com/android/settings/LocalePicker.java b/src/com/android/settings/LocalePicker.java
index 46d9b52..9ee8260 100644
--- a/src/com/android/settings/LocalePicker.java
+++ b/src/com/android/settings/LocalePicker.java
@@ -63,30 +63,55 @@
         setContentView(getContentView());
 
         String[] locales = getAssets().getLocales();
-        final int N = locales.length;
-        mLocales = new Loc[N];
-        for (int i = 0; i < N; i++) {
-            Locale locale = null;
+        Arrays.sort(locales);
+
+        final int origSize = locales.length;
+        Loc[] preprocess = new Loc[origSize];
+        int finalSize = 0;
+        for (int i = 0 ; i < origSize; i++ ) {
             String s = locales[i];
             int len = s.length();
-            if (len == 0) {
-                locale = new Locale("en", "US");
-            } else if (len == 2) {
-                locale = new Locale(s);
+            if (len == 2) {
+                Locale l = new Locale(s);
+                preprocess[finalSize++] = new Loc(l.getDisplayLanguage(), l);
             } else if (len == 5) {
-                locale = new Locale(s.substring(0, 2), s.substring(3, 5));
-            }
-            String displayName = "";
-            if (locale != null) {
-                displayName = locale.getDisplayName();
-            }
-            if ("zz_ZZ".equals(s)) {
-                displayName = "Pseudo...";
-            }
+                String language = s.substring(0, 2);
+                String country = s.substring(3, 5);
+                Locale l = new Locale(language, country);
 
-            mLocales[i] = new Loc(displayName, locale);
+                if (finalSize == 0) {
+                    preprocess[finalSize++] = new Loc(l.getDisplayLanguage(), l);
+                } else {
+                    // check previous entry:
+                    //  same lang and no country -> overwrite it with a lang-only name
+                    //  same lang and a country -> upgrade to full name and 
+                    //    insert ours with full name
+                    //  diff lang -> insert ours with lang-only name
+                    if (preprocess[finalSize-1].locale.getLanguage().equals(language)) {
+                       String prevCountry = preprocess[finalSize-1].locale.getCountry();
+                       if (prevCountry.length() == 0) {
+                            preprocess[finalSize-1].locale = l;
+                            preprocess[finalSize-1].label = l.getDisplayLanguage();
+                        } else {
+                            preprocess[finalSize-1].label = preprocess[finalSize-1].locale.getDisplayName();
+                            preprocess[finalSize++] = new Loc(l.getDisplayName(), l);
+                        }
+                    } else {
+                        String displayName;
+                        if (s.equals("zz_ZZ")) {
+                            displayName = "Pseudo...";
+                        } else {
+                            displayName = l.getDisplayLanguage();
+                        }
+                        preprocess[finalSize++] = new Loc(displayName, l);
+                    }
+                }
+            }
         }
-
+        mLocales = new Loc[finalSize];
+        for (int i = 0; i < finalSize ; i++) {
+            mLocales[i] = preprocess[i];
+        }
         int layoutId = R.layout.locale_picker_item;
         int fieldId = R.id.locale;
         ArrayAdapter<Loc> adapter = new ArrayAdapter<Loc>(this, layoutId, fieldId, mLocales);
@@ -107,25 +132,11 @@
 
             Loc loc = mLocales[position];
             config.locale = loc.locale;
-            final String language = loc.locale.getLanguage();
-            final String region = loc.locale.getCountry();
+
+            // indicate this isn't some passing default - the user wants this remembered
+            config.userSetLocale = true;
 
             am.updateConfiguration(config);
-            
-            // Update the System properties
-            SystemProperties.set("user.language", language);
-            SystemProperties.set("user.region", region);
-            // Write to file for persistence across reboots
-            try {
-                BufferedWriter bw = new BufferedWriter(new java.io.FileWriter(
-                        System.getenv("ANDROID_DATA") + "/locale"));
-                bw.write(language + "_" + region);
-                bw.close();
-            } catch (java.io.IOException ioe) {
-                Log.e(TAG, 
-                        "Unable to persist locale. Error writing to locale file." 
-                        + ioe);
-            }    
         } catch (RemoteException e) {
             // Intentionally left blank
         }
diff --git a/src/com/android/settings/ManageApplications.java b/src/com/android/settings/ManageApplications.java
index 8389502..f1550f9 100644
--- a/src/com/android/settings/ManageApplications.java
+++ b/src/com/android/settings/ManageApplications.java
@@ -18,8 +18,12 @@
 
 import com.android.settings.R;
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ListActivity;
+import android.app.ProgressDialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
@@ -27,320 +31,954 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageStats;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.text.format.Formatter;
 import android.util.Config;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
 import android.widget.AdapterView;
+import android.widget.BaseAdapter;
 import android.widget.ImageView;
 import android.widget.ListView;
-import android.widget.SimpleAdapter;
 import android.widget.TextView;
 import android.widget.AdapterView.OnItemClickListener;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 
 /**
  * Activity to pick an application that will be used to display installation information and
- * options to upgrade/uninstall/delete user data for system applications.
+ * options to uninstall/delete user data for system applications. This activity
+ * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
+ * intent.
  *  Initially a compute in progress message is displayed while the application retrieves
- *  the size information of installed packages which is done asynchronously through a 
- *  handler. Once the computation is done package resource information is retrieved 
- *  and then the information is displayed on the screen. All 
- *  messages are passed through a Handler object.
- *  Known issue: There could be some ordering issues when installing/uninstalling
- *  applications when the application list is being scanned.
+ *  the list of application information from the PackageManager. The size information
+ *  for each package is refreshed to the screen. The resource(app description and
+ *  icon) information for each package is not available yet, so some default values for size
+ *  icon and descriptions are used initially. Later the resource information for each 
+ *  application is retrieved and dynamically updated on the screen.
+ *  A Broadcast receiver registers for package additions or deletions when the activity is
+ *  in focus. If the user installs or deletes packages when the activity has focus, the receiver
+ *  gets notified and proceeds to add/delete these packages from the list on the screen.
+ *  This is an unlikely scenario but could happen. The entire list gets created every time
+ *  the activity's onStart gets invoked. This is to avoid having the receiver for the entire
+ *  life cycle of the application.
+ *  The applications can be sorted either alphabetically or 
+ *  based on size(descending). If this activity gets launched under low memory
+ *  situations(A low memory notification dispatches intent 
+ *  ACTION_MANAGE_PACKAGE_STORAGE) the list is sorted per size.
+ *  If the user selects an application, extended info(like size, uninstall/clear data options,
+ *  permissions info etc.,) is displayed via the InstalledAppDetails activity.
+ *  This activity passes the package name and size information to the 
+ *  InstalledAppDetailsActivity to avoid recomputation of the package size information.
  */
-public class ManageApplications extends Activity implements SimpleAdapter.ViewBinder, OnItemClickListener {
+public class ManageApplications extends ListActivity implements
+        OnItemClickListener, DialogInterface.OnCancelListener {
+    // TAG for this activity
     private static final String TAG = "ManageApplications";
-    //Application prefix information
-    public static final String APP_PKG_PREFIX="com.android.settings.";
-    public static final String APP_PKG_NAME=APP_PKG_PREFIX+"ApplicationPkgName";
-    public static final String APP_PKG_SIZE= APP_PKG_PREFIX+"size";
-    public static final String APP_CHG=APP_PKG_PREFIX+"changed";
     
-    //constant value that can be used to check return code from sub activity.
-    private static final int INSTALLED_APP_DETAILS = 1;
-     //application attributes passed to sub activity that displays more app info
-    private static final String KEY_APP_NAME = "ApplicationName";
-    private static final String KEY_APP_ICON = "ApplicationIcon";
-    private static final String KEY_APP_DESC = "ApplicationDescription";
-    private static final String KEY_APP_SIZE= "ApplicationSize";
-    //sort order that can be changed through the menu
-    public static final int SORT_ORDER_ALPHA = 0;
-    public static final int SORT_ORDER_SIZE = 1;
-   //key and resource values used in constructing map for SimpleAdapter
-    private static final String sKeys[] = new String[] { KEY_APP_NAME, KEY_APP_ICON, 
-            KEY_APP_DESC, KEY_APP_SIZE};
-    private static final int sResourceIds[] = new int[] { R.id.app_name, R.id.app_icon, 
-            R.id.app_description, R.id.app_size};
-    //List of ApplicationInfo objects for various applications
-    private List<ApplicationInfo> mAppList;
-    //SimpleAdapter used for managing items in the list
-    private SimpleAdapter mAppAdapter;
-    //map used to store size information which is used for displaying size information
-    //in this activity as well as the subactivity. this is to avoid invoking package manager
-    //api to retrieve size information
-    private HashMap<String, PackageStats> mSizeMap;
-    private HashMap<String, Map<String, ?> > mAppAdapterMap;
-    //sort order
-    private int mSortOrder = SORT_ORDER_ALPHA;
-    //log information boolean
+    // log information boolean
     private boolean localLOGV = Config.LOGV || false;
-    private ApplicationInfo mCurrentPkg;
-    private int mCurrentPkgIdx = 0;
-    private static final int COMPUTE_PKG_SIZE_START = 1;
-    private static final int COMPUTE_PKG_SIZE_DONE = 2;
-    private static final int REMOVE_PKG=3;
-    private static final int REORDER_LIST=4;
-    private static final int ADD_PKG=5;
-    private static final String ATTR_APP_IDX="ApplicationIndex";
-    private static final String ATTR_CHAINED="Chained";
+    
+    // attributes used as keys when passing values to InstalledAppDetails activity
+    public static final String APP_PKG_PREFIX = "com.android.settings.";
+    public static final String APP_PKG_NAME = APP_PKG_PREFIX+"ApplicationPkgName";
+    public static final String APP_PKG_SIZE = APP_PKG_PREFIX+"size";
+    public static final String APP_CHG = APP_PKG_PREFIX+"changed";
+    
+    // attribute name used in receiver for tagging names of added/deleted packages
     private static final String ATTR_PKG_NAME="PackageName";
+    private static final String ATTR_APP_PKG_STATS="ApplicationPackageStats";
+    
+    // constant value that can be used to check return code from sub activity.
+    private static final int INSTALLED_APP_DETAILS = 1;
+    
+    // sort order that can be changed through the menu can be sorted alphabetically
+    // or size(descending)
+    private static final int MENU_OPTIONS_BASE = 0;
+    public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 0;
+    public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 1;
+    // Filter options used for displayed list of applications
+    public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 2;
+    public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 3;
+    public static final int FILTER_APPS_RUNNING = MENU_OPTIONS_BASE + 4;
+    // sort order
+    private int mSortOrder = SORT_ORDER_ALPHA;
+    // Filter value
+    int mFilterApps = FILTER_APPS_ALL;
+    
+    // Custom Adapter used for managing items in the list
+    private AppInfoAdapter mAppInfoAdapter;
+    
+    // messages posted to the handler
+    private static final int HANDLER_MESSAGE_BASE = 0;
+    private static final int COMPUTE_PKG_SIZE_START = HANDLER_MESSAGE_BASE+1;
+    private static final int COMPUTE_PKG_SIZE_DONE = HANDLER_MESSAGE_BASE+2;
+    private static final int REMOVE_PKG = HANDLER_MESSAGE_BASE+3;
+    private static final int REORDER_LIST = HANDLER_MESSAGE_BASE+4;
+    private static final int ADD_PKG_START = HANDLER_MESSAGE_BASE+5;
+    private static final int ADD_PKG_DONE = HANDLER_MESSAGE_BASE+6;
+    private static final int REFRESH_ICONS = HANDLER_MESSAGE_BASE+7;
+    
+    // observer object used for computing pkg sizes
     private PkgSizeObserver mObserver;
+    // local handle to PackageManager
     private PackageManager mPm;
+    // Broadcast Receiver object that receives notifications for added/deleted
+    // packages
     private PackageIntentReceiver mReceiver;
+    // atomic variable used to track if computing pkg sizes is in progress. should be volatile?
+    
     private boolean mDoneIniting = false;
-    private String mKbStr;
-    private String  mMbStr;
-    private String mBStr;
+    // default icon thats used when displaying applications initially before resource info is
+    // retrieved
+    private Drawable mDefaultAppIcon;
+    
+    // temporary dialog displayed while the application info loads
+    private ProgressDialog mLoadingDlg = null;
+    
+    // compute index used to track the application size computations
+    private int mComputeIndex;
+    
+    // Size resource used for packages whose size computation failed for some reason
+    private CharSequence mInvalidSizeStr;
+    private CharSequence mComputingSizeStr;
+    
+    // map used to store list of added and removed packages. Immutable Boolean
+    // variables indicate if a package has been added or removed. If a package is
+    // added or deleted multiple times a single entry with the latest operation will
+    // be recorded in the map.
+    private Map<String, Boolean> mAddRemoveMap;
+    
+    // layout inflater object used to inflate views
+    private LayoutInflater mInflater;
+    
+    // invalid size value used initially and also when size retrieval through PackageManager
+    // fails for whatever reason
+    private static final int SIZE_INVALID = -1;
+    
+    // debug boolean variable to test delays from PackageManager API's
+    private boolean DEBUG_PKG_DELAY = false;
+    
+    // Thread to load resources
+    ResourceLoaderThread mResourceThread;
+    
+    String mCurrentPkgName;
+    
+    //TODO implement a cache system
+    private Map<String, AppInfo> mAppPropCache;
     
     /*
      * Handler class to handle messages for various operations
+     * Most of the operations that effect Application related data
+     * are posted as messages to the handler to avoid synchronization
+     * when accessing these structures.
+     * When the size retrieval gets kicked off for the first time, a COMPUTE_PKG_SIZE_START
+     * message is posted to the handler which invokes the getSizeInfo for the pkg at index 0
+     * When the PackageManager's asynchronous call back through
+     * PkgSizeObserver.onGetStatsCompleted gets invoked, the application resources like
+     * label, description, icon etc., is loaded in the same thread and these values are
+     * set on the observer. The observer then posts a COMPUTE_PKG_SIZE_DONE message
+     * to the handler. This information is updated on the AppInfoAdapter associated with
+     * the list view of this activity and size info retrieval is initiated for the next package as 
+     * indicated by mComputeIndex
+     * When a package gets added while the activity has focus, the PkgSizeObserver posts
+     * ADD_PKG_START message to the handler.  If the computation is not in progress, the size
+     * is retrieved for the newly added package through the observer object and the newly
+     * installed app info is updated on the screen. If the computation is still in progress
+     * the package is added to an internal structure and action deferred till the computation
+     * is done for all the packages. 
+     * When a package gets deleted, REMOVE_PKG is posted to the handler
+     *  if computation is not in progress(as indicated by
+     * mDoneIniting), the package is deleted from the displayed list of apps. If computation is
+     * still in progress the package is added to an internal structure and action deferred till
+     * the computation is done for all packages.
+     * When the sizes of all packages is computed, the newly
+     * added or removed packages are processed in order.
+     * If the user changes the order in  which these applications are viewed by hitting the
+     * menu key, REORDER_LIST message is posted to the handler. this sorts the list
+     * of items based on the sort order.
      */
     private Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
             PackageStats ps;
             ApplicationInfo info;
             Bundle data;
-            String pkgName;
-            int idx;
-            int size;
-            boolean chained = false;
+            String pkgName = null;
+            AppInfo appInfo;
             data = msg.getData();
+            if(data != null) {
+                pkgName = data.getString(ATTR_PKG_NAME);
+            }
             switch (msg.what) {
             case COMPUTE_PKG_SIZE_START:
-                mDoneIniting = false;
-                //initialize lists
-                mAppList = new ArrayList<ApplicationInfo>();
-                mSizeMap = new HashMap<String, PackageStats>();
-                mAppAdapterMap = new HashMap<String, Map<String, ?> >();
-                //update application list from PackageManager
-                mAppList = mPm.getInstalledApplications(0);
-                if(mAppList.size() == 0) {
-                    return;
-                }
-                mCurrentPkgIdx = 0;
-                mCurrentPkg = mAppList.get(0);
-                if(localLOGV) Log.i(TAG, "Initiating compute sizes for first time");
-                //register receiver
-                mReceiver = new PackageIntentReceiver();
-                mReceiver.registerReceiver();
-                pkgName = mCurrentPkg.packageName;
-                mObserver = new PkgSizeObserver(0);
-                mObserver.invokeGetSizeInfo(pkgName, true);
+                if(localLOGV) Log.i(TAG, "Message COMPUTE_PKG_SIZE_START");
+                setProgressBarIndeterminateVisibility(true);
+                mComputeIndex = 0;
+                initAppList(mFilterApps);
                 break;
             case COMPUTE_PKG_SIZE_DONE:
-                ps = mObserver.ps;
-                info = mObserver.appInfo;
-                chained = data.getBoolean(ATTR_CHAINED);
-                if(!mObserver.succeeded) {
-                    if(chained) {
-                        removePackageFromAppList(ps.packageName);
-                    } else {
-                        //do not go to adding phase
+                if(localLOGV) Log.i(TAG, "Message COMPUTE_PKG_SIZE_DONE");
+                if(pkgName == null) {
+                     Log.w(TAG, "Ignoring message");
+                     break;
+                }
+                ps = data.getParcelable(ATTR_APP_PKG_STATS);
+                if(ps == null) {
+                    Log.i(TAG, "Invalid package stats for package:"+pkgName);
+                } else {
+                    int pkgId = mAppInfoAdapter.getIndex(pkgName);
+                    if(mComputeIndex != pkgId) {
+                        //spurious call from stale observer
+                        Log.w(TAG, "Stale call back from PkgSizeObserver");
                         break;
                     }
-                } else {
-                    //insert size value
-                    mSizeMap.put(ps.packageName, ps);
-                    Map<String, Object> entry = createMapEntry(mPm.getApplicationLabel(info), 
-                            mPm.getApplicationIcon(info), 
-                            info.loadDescription(mPm), 
-                            getSizeStr(ps));
-                    mAppAdapterMap.put(ps.packageName, entry);
+                    mAppInfoAdapter.updateAppSize(pkgName, ps);
                 }
-                if(chained) {
-                    //here app list is precomputed
-                    idx = data.getInt(ATTR_APP_IDX);
-                    //increment only if succeded
-                    if(mObserver.succeeded) {
-                        idx++;
-                    }
-                    if(idx <  mAppList.size()) {
-                        pkgName = mAppList.get(idx).packageName;
-                        //increment record index and invoke getSizeInfo for next record
-                        mObserver.invokeGetSizeInfo(pkgName, true);
-                    } else {
-                        sortAppList();
-                        createListFromValues();
-                        mDoneIniting = true;
-                    }
+                mComputeIndex++;
+                if (mComputeIndex < mAppInfoAdapter.getCount()) {
+                    // initiate compute package size for next pkg in list
+                    mObserver.invokeGetSizeInfo(mAppInfoAdapter.getApplicationInfo(
+                            mComputeIndex), 
+                            COMPUTE_PKG_SIZE_DONE);
                 } else {
-                    //add app info object as well
-                    mAppList.add(info);
-                    sortAppList();
-                    size = mAppList.size();
-                    int i;
-                    for(i = 0; i < size; i++) {
-                        if(mAppList.get(i).packageName.equalsIgnoreCase(mCurrentPkg.packageName)) {
-                            if(i > mCurrentPkgIdx) {
-                                mCurrentPkgIdx = i;
-                            }
-                            break;
+                    // check for added/removed packages
+                    Set<String> keys =  mAddRemoveMap.keySet();
+                    Iterator<String> iter = keys.iterator();
+                    List<String> removeList = new ArrayList<String>();
+                    boolean added = false;
+                    boolean removed = false;
+                    while (iter.hasNext()) {
+                        String key = iter.next();
+                        if (mAddRemoveMap.get(key) == Boolean.TRUE) {
+                            // add
+                            try {
+                                info = mPm.getApplicationInfo(key, 0);
+                                mAppInfoAdapter.addApplicationInfo(info);
+                                added = true;
+                            } catch (NameNotFoundException e) {
+                                Log.w(TAG, "Invalid added package:"+key+" Ignoring entry");
+                            }   
+                        } else {
+                            // remove
+                            removeList.add(key);
+                            removed = true;
                         }
                     }
-                    createListFromValues();
+                    // remove uninstalled packages from list
+                    if (removed) {
+                        mAppInfoAdapter.removeFromList(removeList);
+                    }
+                    // handle newly installed packages
+                    if (added) {
+                        mObserver.invokeGetSizeInfo(mAppInfoAdapter.getApplicationInfo(
+                                mComputeIndex), 
+                                COMPUTE_PKG_SIZE_DONE);
+                    } else {
+                        // end computation here
+                        mDoneIniting = true;
+                        mAppInfoAdapter.sortList(mSortOrder);
+                        //load resources now
+                        if(mResourceThread.isAlive()) {
+                            mResourceThread.interrupt();
+                        }
+                        mResourceThread.loadAllResources(mAppInfoAdapter.getAppList());
+                    }
                 }
                 break;
             case REMOVE_PKG:
-                if(!mDoneIniting) {
-                    //insert message again after some delay
-                    sendMessageToHandler(REMOVE_PKG, data, 10*1000);
+                if(localLOGV) Log.i(TAG, "Message REMOVE_PKG");
+                if(pkgName == null) {
+                    Log.w(TAG, "Ignoring message:REMOVE_PKG for null pkgName");
                     break;
                 }
-                pkgName = data.getString(ATTR_PKG_NAME);
-                removePackageFromAppList(pkgName);
-                if(mSizeMap.remove(pkgName) == null) {
-                    Log.i(TAG, "Coudnt remove from size map package:"+pkgName);
-                }
-                if(mAppAdapterMap.remove(pkgName) == null) {
-                    Log.i(TAG, "Coudnt remove from app adapter map package:"+pkgName);
-                }
-                if(mCurrentPkg.packageName.equalsIgnoreCase(pkgName)) {
-                    if(mCurrentPkgIdx == (mAppList.size()-1)) {
-                        mCurrentPkgIdx--;
+                if (!mDoneIniting) {
+                    Boolean currB = mAddRemoveMap.get(pkgName);
+                    if (currB == null || (currB.equals(Boolean.TRUE))) {
+                        mAddRemoveMap.put(pkgName, Boolean.FALSE);
                     }
-                    mCurrentPkg = mAppList.get(mCurrentPkgIdx);
+                    break;
                 }
-                createListFromValues();
+                List<String> pkgList = new ArrayList<String>();
+                pkgList.add(pkgName);
+                mAppInfoAdapter.removeFromList(pkgList);
                 break;
             case REORDER_LIST:
-                int sortOrder = msg.arg1;
-                if(sortOrder != mSortOrder) {
-                    mSortOrder = sortOrder;
-                    if(localLOGV) Log.i(TAG, "Changing sort order to "+mSortOrder);
-                    sortAppList();
-                    mCurrentPkgIdx  = 0;
-                    mCurrentPkg = mAppList.get(mCurrentPkgIdx);
-                    createListFromValues();
+                if(localLOGV) Log.i(TAG, "Message REORDER_LIST");
+                int menuOption = msg.arg1;
+                if((menuOption == SORT_ORDER_ALPHA) || 
+                        (menuOption == SORT_ORDER_SIZE)) {
+                    // Option to sort list
+                    if (menuOption != mSortOrder) {
+                        mSortOrder = menuOption;
+                        if (localLOGV) Log.i(TAG, "Changing sort order to "+mSortOrder);
+                        mAppInfoAdapter.sortList(mSortOrder);
+                    }
+                } else if(menuOption != mFilterApps) {
+                    // Option to filter list
+                    mFilterApps = menuOption;
+                    boolean ret = mAppInfoAdapter.resetAppList(mFilterApps, 
+                            getInstalledApps(mFilterApps));
+                    if(!ret) {
+                        // Reset cache
+                        mAppPropCache = null;
+                        mFilterApps = FILTER_APPS_ALL;
+                        mHandler.sendEmptyMessage(COMPUTE_PKG_SIZE_START);
+                        sendMessageToHandler(REORDER_LIST, menuOption);
+                    }
                 }
                 break;
-            case ADD_PKG:
-                pkgName = data.getString(ATTR_PKG_NAME);
-                if(!mDoneIniting) {
-                   //insert message again after some delay
-                    sendMessageToHandler(ADD_PKG, data, 10*1000);
+            case ADD_PKG_START:
+                if(localLOGV) Log.i(TAG, "Message ADD_PKG_START");
+                if(pkgName == null) {
+                    Log.w(TAG, "Ignoring message:ADD_PKG_START for null pkgName");
                     break;
                 }
-                mObserver.invokeGetSizeInfo(pkgName, false);
+                if (!mDoneIniting) {
+                    Boolean currB = mAddRemoveMap.get(pkgName);
+                    if (currB == null || (currB.equals(Boolean.FALSE))) {
+                        mAddRemoveMap.put(pkgName, Boolean.TRUE);
+                    }
+                    break;
+                }
+                try {
+                        info = mPm.getApplicationInfo(pkgName, 0);
+                    } catch (NameNotFoundException e) {
+                        Log.w(TAG, "Couldnt find application info for:"+pkgName);
+                        break;
+                    }
+                mObserver.invokeGetSizeInfo(info, ADD_PKG_DONE);
                 break;
+            case ADD_PKG_DONE:
+                if(localLOGV) Log.i(TAG, "Message COMPUTE_PKG_SIZE_DONE");
+                if(pkgName == null) {
+                    Log.w(TAG, "Ignoring message:ADD_PKG_START for null pkgName");
+                    break;
+                }
+                ps = data.getParcelable(ATTR_APP_PKG_STATS);
+                mAppInfoAdapter.addToList(pkgName, ps);
+                break;
+            case REFRESH_ICONS:
+                Map<String, AppInfo> iconMap = (Map<String, AppInfo>) msg.obj;
+                if(iconMap == null) {
+                    Log.w(TAG, "Error loading icons for applications");
+                } else {
+                    mAppInfoAdapter.updateAppsResourceInfo(iconMap);
+                    setProgressBarIndeterminateVisibility(false);
+                }
             default:
                 break;
             }
         }
     };
     
-    private void removePackageFromAppList(String pkgName) {
-        int size = mAppList.size();
-        for(int i = 0; i < size; i++) {
-            if(mAppList.get(i).packageName.equalsIgnoreCase(pkgName)) {
-                mAppList.remove(i);
-                break;
+    List<ApplicationInfo> getInstalledApps(int filterOption) {
+        List<ApplicationInfo> installedAppList = mPm.getInstalledApplications(
+                PackageManager.GET_UNINSTALLED_PACKAGES);
+        if (installedAppList == null) {
+            return new ArrayList<ApplicationInfo> ();
+        }
+        if (filterOption == FILTER_APPS_THIRD_PARTY) {
+            List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> ();
+            for (ApplicationInfo appInfo : installedAppList) {
+                if ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+                    appList.add(appInfo);
+                }
             }
-        }
-    }
-    
-    private void clearMessages() {
-        synchronized(mHandler) {
-            mHandler.removeMessages(COMPUTE_PKG_SIZE_START);
-            mHandler.removeMessages(COMPUTE_PKG_SIZE_DONE);
-            mHandler.removeMessages(REMOVE_PKG);
-            mHandler.removeMessages(REORDER_LIST);
-            mHandler.removeMessages(ADD_PKG);
-        }
-    }
-    
-    private void sendMessageToHandler(int msgId, Bundle data, long delayMillis) {
-        synchronized(mHandler) {
-            Message msg = mHandler.obtainMessage(msgId);
-            msg.setData(data);
-            if(delayMillis == 0) {
-                mHandler.sendMessage(msg);
-            } else {
-                mHandler.sendMessageDelayed(msg, delayMillis);
+            return appList;
+        } else if (filterOption == FILTER_APPS_RUNNING) {
+            List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> ();
+            List<ActivityManager.RunningAppProcessInfo> procList = getRunningAppProcessesList();
+            if ((procList == null) || (procList.size() == 0)) {
+                return appList;
             }
+            // Retrieve running processes from ActivityManager
+            for (ActivityManager.RunningAppProcessInfo appProcInfo : procList) {
+                if ((appProcInfo != null)  && (appProcInfo.pkgList != null)){
+                    int size = appProcInfo.pkgList.length;
+                    for (int i = 0; i < size; i++) {
+                        ApplicationInfo appInfo = null;
+                        try {
+                            appInfo = mPm.getApplicationInfo(appProcInfo.pkgList[i], 
+                                    PackageManager.GET_UNINSTALLED_PACKAGES);
+                        } catch (NameNotFoundException e) {
+                           Log.w(TAG, "Error retrieving ApplicationInfo for pkg:"+appProcInfo.pkgList[i]);
+                           continue;
+                        }
+                        if(appInfo != null) {
+                            appList.add(appInfo);
+                        }
+                    }
+                }
+            }
+            return appList;
+        } else {
+            return installedAppList;
         }
     }
     
-    private void sendMessageToHandler(int msgId, int arg1) {
-        synchronized(mHandler) {
-            Message msg = mHandler.obtainMessage(msgId);
-            msg.arg1 = arg1;
+    private List<ActivityManager.RunningAppProcessInfo> getRunningAppProcessesList() {
+        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+        return am.getRunningAppProcesses();
+    }
+    
+    // some initialization code used when kicking off the size computation
+    private void initAppList(int filterOption) {
+        mDoneIniting = false;
+        // Initialize lists
+        List<ApplicationInfo> appList = getInstalledApps(filterOption);
+        mAddRemoveMap = new TreeMap<String, Boolean>();
+        mAppInfoAdapter = new AppInfoAdapter(this, appList);
+        dismissLoadingMsg();
+        // get list and set listeners and adapter
+        ListView lv= (ListView) findViewById(android.R.id.list);
+        lv.setOnItemClickListener(this);
+        lv.setSaveEnabled(true);
+        lv.setItemsCanFocus(true);
+        lv.setOnItemClickListener(this);
+        lv.setAdapter(mAppInfoAdapter);
+        // register receiver
+        mReceiver = new PackageIntentReceiver();
+        mReceiver.registerReceiver();
+        // initiate compute pkg sizes
+        if (localLOGV) Log.i(TAG, "Initiating compute sizes for first time");
+        mObserver = new PkgSizeObserver();
+        if(appList.size() > 0) {
+            mObserver.invokeGetSizeInfo(appList.get(0), COMPUTE_PKG_SIZE_DONE);
+        } else {
+            mDoneIniting = true;
+        }
+    }
+    
+    // internal structure used to track added and deleted packages when
+    // the activity has focus
+    class AddRemoveInfo {
+        String pkgName;
+        boolean add;
+        public AddRemoveInfo(String pPkgName, boolean pAdd) {
+            pkgName = pPkgName;
+            add = pAdd;
+        }
+    }
+    
+    class ResourceLoaderThread extends Thread {
+        List<ApplicationInfo> mAppList;
+        
+        void loadAllResources(List<ApplicationInfo> appList) {
+            if(appList == null || appList.size() <= 0) {
+                Log.w(TAG, "Empty or null application list");
+                return;
+            }
+            mAppList = appList;
+            start();
+        }
+
+        public void run() {
+            Map<String, AppInfo> iconMap = new HashMap<String, AppInfo>();
+            for (ApplicationInfo appInfo : mAppList) {
+                CharSequence appName = appInfo.loadLabel(mPm);
+                Drawable appIcon = appInfo.loadIcon(mPm);
+                iconMap.put(appInfo.packageName, 
+                        new AppInfo(appInfo.packageName, appName, appIcon));
+            }
+            Message msg = mHandler.obtainMessage(REFRESH_ICONS);
+            msg.obj = iconMap;
             mHandler.sendMessage(msg);
         }
     }
     
-    private void sendMessageToHandler(int msgId) {
-        synchronized(mHandler) {
-            mHandler.sendEmptyMessage(msgId);
+    /* Internal class representing an application or packages displayable attributes
+     * 
+     */
+    class AppInfo {
+        public String pkgName;
+        int index;
+        public  CharSequence appName;
+        public  Drawable appIcon;
+        public CharSequence appSize;
+        public PackageStats appStats;
+        
+        public void refreshIcon(AppInfo pInfo) {
+            appName = pInfo.appName;
+            appIcon = pInfo.appIcon;
+        }
+
+        public AppInfo(String pName, CharSequence aName, Drawable aIcon) {
+            index = -1;
+            pkgName = pName;
+            appName = aName;
+            appIcon = aIcon;
+            appStats = null;
+            appSize = mComputingSizeStr;
+        }
+        
+        public AppInfo(String pName, int pIndex, CharSequence aName, Drawable aIcon, 
+                PackageStats ps) {
+            index = pIndex;
+            pkgName = pName;
+            appName = aName;
+            appIcon = aIcon;
+            if(ps == null) {
+                appSize = mComputingSizeStr;
+            } else {
+                appStats = ps;
+                appSize = getSizeStr();
+            }
+        }
+        public void setSize(PackageStats ps) {
+            appStats = ps;
+            if (ps != null) {
+                appSize = getSizeStr();
+            }
+        }
+        public long getTotalSize() {
+            PackageStats ps = appStats;
+            if (ps != null) {
+                return ps.cacheSize+ps.codeSize+ps.dataSize;
+            }
+            return SIZE_INVALID;
+        }
+        
+        private String getSizeStr() {
+            PackageStats ps = appStats;
+            String retStr = "";
+            // insert total size information into map to display in view
+            // at this point its guaranteed that ps is not null. but checking anyway
+            if (ps != null) {
+                long size = getTotalSize();
+                if (size == SIZE_INVALID) {
+                    return mInvalidSizeStr.toString();
+                }
+                return Formatter.formatFileSize(ManageApplications.this, size);
+            }
+            return retStr;
         }
     }
     
-    class PkgSizeObserver extends IPackageStatsObserver.Stub {
-        public PackageStats ps;
-        public ApplicationInfo appInfo;
-        public Drawable appIcon;
-        public CharSequence appName;
-        public CharSequence appDesc = "";
-        private int mIdx = 0;
-        private boolean mChained = false;
-        public boolean succeeded;
-        PkgSizeObserver(int i) {
-            mIdx = i;
-        }
+    // View Holder used when displaying views
+    static class AppViewHolder {
+        TextView appName;
+        ImageView appIcon;
+        TextView appSize;
+    }
+    
+    /* Custom adapter implementation for the ListView
+     * This adapter maintains a map for each displayed application and its properties
+     * An index value on each AppInfo object indicates the correct position or index
+     * in the list. If the list gets updated dynamically when the user is viewing the list of
+     * applications, we need to return the correct index of position. This is done by mapping
+     * the getId methods via the package name into the internal maps and indices.
+     * The order of applications in the list is mirrored in mAppLocalList
+     */
+    class AppInfoAdapter extends BaseAdapter {
+        private Map<String, AppInfo> mAppPropMap;
+        private List<ApplicationInfo> mAppLocalList;
+        ApplicationInfo.DisplayNameComparator mAlphaComparator;
+        AppInfoComparator mSizeComparator;
         
-        private void getAppDetails() {
-            try {
-                appInfo = mPm.getApplicationInfo(ps.packageName, 0);
-            } catch (NameNotFoundException e) {
-                return;
+        private AppInfo getFromCache(String packageName) {
+            if(mAppPropCache == null) {
+                return null;
             }
-            appName = appInfo.loadLabel(mPm);
-            appIcon = appInfo.loadIcon(mPm);
+            return mAppPropCache.get(packageName);
         }
         
-        public void onGetStatsCompleted(PackageStats pStats, boolean pSucceeded) {
-            Bundle data = new Bundle();
-            ps = pStats;
-            succeeded = pSucceeded;
-            if(mChained) {
-                data.putInt(ATTR_APP_IDX, mIdx);
-                if(succeeded) {
-                    mIdx++;
+        public AppInfoAdapter(Context c, List<ApplicationInfo> appList) {
+            mAppLocalList = appList;
+            boolean useCache = false;
+            int sortOrder = SORT_ORDER_ALPHA;
+            int imax = mAppLocalList.size();
+            if(mAppPropCache != null) {
+                useCache = true;
+                // Activity has been resumed. can use the cache to populate values initially
+                mAppPropMap = mAppPropCache;
+                sortOrder = mSortOrder;
+            }
+            sortAppList(sortOrder);
+            // Recreate property map
+            mAppPropMap = new TreeMap<String, AppInfo>();
+            for (int i = 0; i < imax; i++) {
+                ApplicationInfo info = mAppLocalList.get(i);
+                AppInfo aInfo = getFromCache(info.packageName);
+                if(aInfo == null){
+                    aInfo = new AppInfo(info.packageName, i, 
+                            info.packageName, mDefaultAppIcon, null);   
+                } else {
+                    aInfo.index = i;
+                }
+                mAppPropMap.put(info.packageName, aInfo);
+            }
+        }
+        
+        public int getCount() {
+            return mAppLocalList.size();
+        }
+        
+        public Object getItem(int position) {
+            return mAppLocalList.get(position);
+        }
+        
+        /*
+         * This method returns the index of the package position in the application list
+         */
+        public int getIndex(String pkgName) {
+            if(pkgName == null) {
+                Log.w(TAG, "Getting index of null package in List Adapter");
+            }
+            int imax = mAppLocalList.size();
+            ApplicationInfo appInfo;
+            for(int i = 0; i < imax; i++) {
+                appInfo = mAppLocalList.get(i);
+                if(appInfo.packageName.equalsIgnoreCase(pkgName)) {
+                    return i;
                 }
             }
-            data.putBoolean(ATTR_CHAINED, mChained);
-            getAppDetails();
-            if(localLOGV) Log.i(TAG, "onGetStatsCompleted::"+appInfo.packageName+", ("+ps.cacheSize+","+
-                    ps.codeSize+", "+ps.dataSize);
-            sendMessageToHandler(COMPUTE_PKG_SIZE_DONE, data, 0);
+            return -1;
         }
         
-        public void invokeGetSizeInfo(String packageName, boolean chained) {
-             mChained = chained;
-             mPm.getPackageSizeInfo(packageName, this);
+        public ApplicationInfo getApplicationInfo(int position) {
+            int imax = mAppLocalList.size();
+            if( (position < 0) || (position >= imax)) {
+                Log.w(TAG, "Position out of bounds in List Adapter");
+                return null;
+            }
+            return mAppLocalList.get(position);
+        }
+        
+        public void addApplicationInfo(ApplicationInfo info) {
+            if(info == null) {
+                Log.w(TAG, "Ignoring null add in List Adapter");
+                return;
+            }
+            mAppLocalList.add(info);
+        }
+
+        public long getItemId(int position) {
+            int imax = mAppLocalList.size();
+            if( (position < 0) || (position >= imax)) {
+                Log.w(TAG, "Position out of bounds in List Adapter");
+                return -1;
+            }
+            return mAppPropMap.get(mAppLocalList.get(position).packageName).index;
+        }
+        
+        public List<ApplicationInfo> getAppList() {
+            return mAppLocalList;
+        }
+        
+        public View getView(int position, View convertView, ViewGroup parent) {
+            // A ViewHolder keeps references to children views to avoid unneccessary calls
+            // to findViewById() on each row.
+            AppViewHolder holder;
+
+            // When convertView is not null, we can reuse it directly, there is no need
+            // to reinflate it. We only inflate a new View when the convertView supplied
+            // by ListView is null.
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.manage_applications_item, null);
+
+                // Creates a ViewHolder and store references to the two children views
+                // we want to bind data to.
+                holder = new AppViewHolder();
+                holder.appName = (TextView) convertView.findViewById(R.id.app_name);
+                holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
+                holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
+                convertView.setTag(holder);
+            } else {
+                // Get the ViewHolder back to get fast access to the TextView
+                // and the ImageView.
+                holder = (AppViewHolder) convertView.getTag();
+            }
+
+            // Bind the data efficiently with the holder
+            ApplicationInfo appInfo = mAppLocalList.get(position);
+            AppInfo mInfo = mAppPropMap.get(appInfo.packageName);
+            if(mInfo != null) {
+                if(mInfo.appName != null) {
+                    holder.appName.setText(mInfo.appName);
+                }
+                if(mInfo.appIcon != null) {
+                    holder.appIcon.setImageDrawable(mInfo.appIcon);
+                }
+                holder.appSize.setText(mInfo.appSize);
+            } else {
+                Log.w(TAG, "No info for package:"+appInfo.packageName+" in property map");
+            }
+            return convertView;
+        }
+        
+        private void adjustIndex() {
+            int imax = mAppLocalList.size();
+            ApplicationInfo info;
+            for (int i = 0; i < imax; i++) {
+                info = mAppLocalList.get(i);
+                mAppPropMap.get(info.packageName).index = i;
+            }
+        }
+        
+        public void sortAppList(int sortOrder) {
+            Collections.sort(mAppLocalList, getAppComparator(sortOrder));
+        }
+        
+        public void sortList(int sortOrder) {
+            sortAppList(sortOrder);
+            adjustIndex();
+            notifyDataSetChanged();
+        }
+        
+        public boolean resetAppList(int filterOption, List<ApplicationInfo> appList) {
+           // Create application list based on the filter value
+           mAppLocalList = appList;
+           // Check for all properties in map before sorting. Populate values from cache
+           for(ApplicationInfo applicationInfo : mAppLocalList) {
+               AppInfo appInfo = mAppPropMap.get(applicationInfo.packageName);
+               if(appInfo == null) {
+                   AppInfo rInfo = getFromCache(applicationInfo.packageName);
+                   if(rInfo == null) {
+                       // Need to load resources again. Inconsistency somewhere
+                       return false;
+                   }
+                   mAppPropMap.put(applicationInfo.packageName, rInfo);
+               }
+           }
+           sortList(mSortOrder);
+           return true;
+        }
+        
+        private Comparator<ApplicationInfo> getAppComparator(int sortOrder) {
+            if (sortOrder == SORT_ORDER_ALPHA) {
+                // Lazy initialization
+                if (mAlphaComparator == null) {
+                    mAlphaComparator = new ApplicationInfo.DisplayNameComparator(mPm);
+                }
+                return mAlphaComparator;
+            }
+            // Lazy initialization
+            if(mSizeComparator == null) {
+                mSizeComparator = new AppInfoComparator(mAppPropMap);
+            }
+            return mSizeComparator;
+        }
+        
+        public void updateAppsResourceInfo(Map<String, AppInfo> iconMap) {
+            if(iconMap == null) {
+                Log.w(TAG, "Null iconMap when refreshing icon in List Adapter");
+                return;
+            }
+            boolean changed = false;
+            for (ApplicationInfo info : mAppLocalList) {
+                AppInfo pInfo = iconMap.get(info.packageName);
+                if(pInfo != null) {
+                    AppInfo aInfo = mAppPropMap.get(info.packageName);
+                    aInfo.refreshIcon(pInfo);
+                    changed = true;
+                }
+            }
+            if(changed) {
+                notifyDataSetChanged();
+            }
+        }
+        
+        public void addToList(String pkgName, PackageStats ps) {
+            if(pkgName == null) {
+                Log.w(TAG, "Adding null pkg to List Adapter");
+                return;
+            }
+            ApplicationInfo info;
+            try {
+                info = mPm.getApplicationInfo(pkgName, 0);
+            } catch (NameNotFoundException e) {
+                Log.w(TAG, "Ignoring non-existent package:"+pkgName);
+                return;
+            }
+            if(info == null) {
+                // Nothing to do log error message and return
+                Log.i(TAG, "Null ApplicationInfo for package:"+pkgName);
+                return;
+            }
+            // Binary search returns a negative index (ie --index) of the position where
+            // this might be inserted. 
+            int newIdx = Collections.binarySearch(mAppLocalList, info, 
+                    getAppComparator(mSortOrder));
+            if(newIdx >= 0) {
+                Log.i(TAG, "Strange. Package:"+pkgName+" is not new");
+                return;
+            }
+            // New entry
+            newIdx = -newIdx-1;
+            mAppLocalList.add(newIdx, info);
+            mAppPropMap.put(info.packageName, new AppInfo(pkgName, newIdx,
+                    info.loadLabel(mPm), info.loadIcon(mPm), ps));
+            adjustIndex();
+            notifyDataSetChanged();
+        }
+        
+        public void removeFromList(List<String> pkgNames) {
+            if(pkgNames == null) {
+                Log.w(TAG, "Removing null pkg list from List Adapter");
+                return;
+            }
+            int imax = mAppLocalList.size();
+            boolean found = false;
+            ApplicationInfo info;
+            int i, k;
+            String pkgName;
+            int kmax = pkgNames.size();
+            if(kmax  <= 0) {
+                Log.w(TAG, "Removing empty pkg list from List Adapter");
+                return;
+            }
+            int idxArr[] = new int[kmax];
+            for (k = 0; k < kmax; k++) {
+                idxArr[k] = -1;
+            }
+            for (i = 0; i < imax; i++) {
+                info = mAppLocalList.get(i);
+                for (k = 0; k < kmax; k++) {
+                    pkgName = pkgNames.get(k);
+                    if (info.packageName.equalsIgnoreCase(pkgName)) {
+                        idxArr[k] = i;
+                        found = true;
+                        break;
+                    }
+                }
+            }
+            // Sort idxArr
+            Arrays.sort(idxArr);
+            // remove the packages based on decending indices
+            for (k = kmax-1; k >= 0; k--) {
+                // Check if package has been found in the list of existing apps first
+                if(idxArr[k] == -1) {
+                    break;
+                }
+                info = mAppLocalList.get(idxArr[k]);
+                mAppLocalList.remove(idxArr[k]);
+                mAppPropMap.remove(info.packageName);
+                if (localLOGV) Log.i(TAG, "Removed pkg:"+info.packageName+ " list");
+            }
+            if (found) {
+                adjustIndex();
+                notifyDataSetChanged();
+            }
+        }   
+        
+        public void updateAppSize(String pkgName, PackageStats ps) {
+            if(pkgName == null) {
+                return;
+            }
+            AppInfo entry = mAppPropMap.get(pkgName);
+            if (entry == null) {
+                Log.w(TAG, "Entry for package:"+pkgName+"doesnt exist in map");
+                return;
+            }
+            // Copy the index into the newly updated entry
+            entry.setSize(ps);
+            notifyDataSetChanged();
+        }
+
+        public PackageStats getAppStats(String pkgName) {
+            if(pkgName == null) {
+                return null;
+            }
+            AppInfo entry = mAppPropMap.get(pkgName);
+            if (entry == null) {
+                return null;
+            }
+            return entry.appStats;
+        }
+    }
+    
+    /*
+     * Utility method to clear messages to Handler
+     * We need'nt synchronize on the Handler since posting messages is guaranteed
+     * to be thread safe. Even if the other thread that retrieves package sizes
+     * posts a message, we do a cursory check of validity on mAppInfoAdapter's applist
+     */
+    private void clearMessagesInHandler() {
+        mHandler.removeMessages(COMPUTE_PKG_SIZE_START);
+        mHandler.removeMessages(COMPUTE_PKG_SIZE_DONE);
+        mHandler.removeMessages(REMOVE_PKG);
+        mHandler.removeMessages(REORDER_LIST);
+        mHandler.removeMessages(ADD_PKG_START);
+        mHandler.removeMessages(ADD_PKG_DONE);
+    }
+    
+    private void sendMessageToHandler(int msgId, int arg1) {
+        Message msg = mHandler.obtainMessage(msgId);
+        msg.arg1 = arg1;
+        mHandler.sendMessage(msg);
+    }
+    
+    private void sendMessageToHandler(int msgId, Bundle data) {
+        Message msg = mHandler.obtainMessage(msgId);
+        msg.setData(data);
+        mHandler.sendMessage(msg);
+    }
+    
+    private void sendMessageToHandler(int msgId) {
+        mHandler.sendEmptyMessage(msgId);
+    }
+    
+    /*
+     * Stats Observer class used to compute package sizes and retrieve size information
+     * PkgSizeOberver is the call back thats used when invoking getPackageSizeInfo on
+     * PackageManager. The values in call back onGetStatsCompleted are validated
+     * and the specified message is passed to mHandler. The package name
+     * and the AppInfo object corresponding to the package name are set on the message
+     */
+    class PkgSizeObserver extends IPackageStatsObserver.Stub {
+        private ApplicationInfo mAppInfo;
+        private int mMsgId; 
+        public void onGetStatsCompleted(PackageStats pStats, boolean pSucceeded) {
+            if(DEBUG_PKG_DELAY) {
+                try {
+                    Thread.sleep(10*1000);
+                } catch (InterruptedException e) {
+                }
+            }
+            AppInfo appInfo = null;
+            Bundle data = new Bundle();
+            data.putString(ATTR_PKG_NAME, mAppInfo.packageName);
+            if(pSucceeded && pStats != null) {
+                if (localLOGV) Log.i(TAG, "onGetStatsCompleted::"+pStats.packageName+", ("+
+                        pStats.cacheSize+","+
+                        pStats.codeSize+", "+pStats.dataSize);
+                data.putParcelable(ATTR_APP_PKG_STATS, pStats);
+            } else {
+                Log.w(TAG, "Invalid package stats from PackageManager");
+            }
+            //post message to Handler
+            Message msg = mHandler.obtainMessage(mMsgId, data);
+            msg.setData(data);
+            mHandler.sendMessage(msg);
+        }
+
+        public void invokeGetSizeInfo(ApplicationInfo pAppInfo, int msgId) {
+            if(pAppInfo == null || pAppInfo.packageName == null) {
+                return;
+            }
+            if(localLOGV) Log.i(TAG, "Invoking getPackageSizeInfo for package:"+
+                    pAppInfo.packageName);
+            mMsgId = msgId;
+            mAppInfo = pAppInfo;
+            mPm.getPackageSizeInfo(pAppInfo.packageName, this);
         }
     }
     
@@ -360,239 +998,147 @@
             String actionStr = intent.getAction();
             Uri data = intent.getData();
             String pkgName = data.getEncodedSchemeSpecificPart();
-            if(localLOGV) Log.i(TAG, "action:"+actionStr+", for package:"+pkgName);
+            if (localLOGV) Log.i(TAG, "action:"+actionStr+", for package:"+pkgName);
             updatePackageList(actionStr, pkgName);
         }
     }
     
     private void updatePackageList(String actionStr, String pkgName) {
-        //technically we dont have to invoke handler since onReceive is invoked on
-        //the main thread but doing it here for better clarity
-        if(Intent.ACTION_PACKAGE_ADDED.equalsIgnoreCase(actionStr)) {
+        // technically we dont have to invoke handler since onReceive is invoked on
+        // the main thread but doing it here for better clarity
+        if (Intent.ACTION_PACKAGE_ADDED.equalsIgnoreCase(actionStr)) {
             Bundle data = new Bundle();
             data.putString(ATTR_PKG_NAME, pkgName);
-            sendMessageToHandler(ADD_PKG, data, 0);
-        } else if(Intent.ACTION_PACKAGE_REMOVED.equalsIgnoreCase(actionStr)) {
+            sendMessageToHandler(ADD_PKG_START, data);
+        } else if (Intent.ACTION_PACKAGE_REMOVED.equalsIgnoreCase(actionStr)) {
             Bundle data = new Bundle();
             data.putString(ATTR_PKG_NAME, pkgName);
-            sendMessageToHandler(REMOVE_PKG, data, 0);
-        } else if(Intent.ACTION_PACKAGE_CHANGED.equalsIgnoreCase(actionStr)) {
-            //force adapter to draw the list again. TODO derive from SimpleAdapter
-            //to avoid this
-           
-        }   
-    }
-    
-    /*
-     * Utility method to create an array of map objects from a map of map objects
-     *  for displaying list items to be used in SimpleAdapter.
-     */
-    private void createListFromValues() {
-        findViewById(R.id.center_text).setVisibility(View.GONE);
-        populateAdapterList();
-        mAppAdapter.setViewBinder(this);
-        ListView lv= (ListView) findViewById(android.R.id.list);
-        lv.setOnItemClickListener(this);
-        lv.setAdapter(mAppAdapter);
-        if(mCurrentPkgIdx != -1) {
-            lv.setSelection(mCurrentPkgIdx);
+            sendMessageToHandler(REMOVE_PKG, data);
         }
     }
     
+    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        String action = getIntent().getAction();
-        if(action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
+        Intent lIntent = getIntent();
+        String action = lIntent.getAction();
+        if (action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
             mSortOrder = SORT_ORDER_SIZE;
         }
         mPm = getPackageManager();
-        //load strings from resources
-        mBStr = getString(R.string.b_text);
-        mKbStr = getString(R.string.kb_text);
-        mMbStr = getString(R.string.mb_text);
+        // initialize some window features
+        requestWindowFeature(Window.FEATURE_RIGHT_ICON);
+        requestWindowFeature(Window.FEATURE_PROGRESS);
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        // init mLoadingDlg
+        mLoadingDlg = new ProgressDialog(this);
+        mLoadingDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+        mLoadingDlg.setMessage(getText(R.string.loading));
+        mLoadingDlg.setIndeterminate(true);        
+        mLoadingDlg.setOnCancelListener(this);
+        mDefaultAppIcon =Resources.getSystem().getDrawable(
+                com.android.internal.R.drawable.sym_def_app_icon);
+        mInvalidSizeStr = getText(R.string.invalid_size_value);
+        mComputingSizeStr = getText(R.string.computing_size);
+        // initialize the inflater
+        mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+    
+    private void showLoadingMsg() {
+        if (mLoadingDlg != null) {
+            if(localLOGV) Log.i(TAG, "Displaying Loading message");
+            mLoadingDlg.show();
+        }
+    }
+    
+    private void dismissLoadingMsg() {
+        if ((mLoadingDlg != null) && (mLoadingDlg.isShowing())) {
+            if(localLOGV) Log.i(TAG, "Dismissing Loading message");
+            mLoadingDlg.dismiss();
+        }
     }
     
     @Override
     public void onStart() {
         super.onStart();
         setContentView(R.layout.compute_sizes);
-        //clear all messages related to application list
-        clearMessages();
+        showLoadingMsg();
+        // Create a thread to load resources
+        mResourceThread = new ResourceLoaderThread();
         sendMessageToHandler(COMPUTE_PKG_SIZE_START);
     }
 
     @Override
     public void onStop() {
         super.onStop();
-        //register receiver here
+        // clear all messages related to application list
+        clearMessagesInHandler();
+        // register receiver here
         unregisterReceiver(mReceiver);        
+        mAppPropCache = mAppInfoAdapter.mAppPropMap;
     }
     
+    /*
+     * comparator class used to sort AppInfo objects based on size
+     */
     public static class AppInfoComparator implements Comparator<ApplicationInfo> {
-        public AppInfoComparator(HashMap<String, PackageStats> pSizeMap) {
-            mSizeMap= pSizeMap;
+        public AppInfoComparator(Map<String, AppInfo> pAppPropMap) {
+            mAppPropMap= pAppPropMap;
         }
 
         public final int compare(ApplicationInfo a, ApplicationInfo b) {
-            PackageStats aps, bps;
-            aps = mSizeMap.get(a.packageName);
-            bps = mSizeMap.get(b.packageName);
-            if (aps == null && bps == null) {
-                return 0;
-            } else if (aps == null) {
-                return 1;
-            } else if (bps == null) {
-                return -1;
-            }
-            long atotal = aps.dataSize+aps.codeSize+aps.cacheSize;
-            long btotal = bps.dataSize+bps.codeSize+bps.cacheSize;
-            long ret = atotal-btotal;
-            //negate result to sort in descending order
-            if(ret < 0) {
+            AppInfo ainfo = mAppPropMap.get(a.packageName);
+            AppInfo binfo = mAppPropMap.get(b.packageName);
+            long atotal = ainfo.getTotalSize();
+            long btotal = binfo.getTotalSize();
+            long ret = atotal - btotal;
+            // negate result to sort in descending order
+            if (ret < 0) {
                 return 1;
             }
-            if(ret == 0) {
+            if (ret == 0) {
                 return 0;
             }
             return -1;
         }
-        private HashMap<String, PackageStats> mSizeMap;
+        private Map<String, AppInfo> mAppPropMap;
     }
-
-    /*
-     * Have to extract elements form map and populate a list ot be used by
-     * SimpleAdapter when displaying list elements. The sort order has to follow
-     * the order of elements in mAppList.
-     */
-     private List<Map<String, ?>> createAdapterListFromMap() {
-         //get the index from mAppInfo which gives the correct sort position
-         int imax = mAppList.size();
-         if(localLOGV) Log.i(TAG, "Creating new adapter list");
-         List<Map<String, ?>> adapterList = new ArrayList<Map<String, ?>>();
-         ApplicationInfo tmpInfo;
-         for(int i = 0; i < imax; i++) {
-             tmpInfo = mAppList.get(i);
-             Map<String, Object>newObj = new TreeMap<String, Object>(
-                     mAppAdapterMap.get(tmpInfo.packageName));
-             adapterList.add(newObj);
-         }
-         return adapterList;
-     }
      
-    private void populateAdapterList() {
-        mAppAdapter = new SimpleAdapter(this, createAdapterListFromMap(),
-                    R.layout.manage_applications_item, sKeys, sResourceIds);
-    }
-    
-    private String getSizeStr(PackageStats ps) {
-        String retStr = "";
-        //insert total size information into map to display in view
-        //at this point its guaranteed that ps is not null. but checking anyway
-        if(ps != null) {
-            long size = ps.cacheSize+ps.codeSize+ps.dataSize;
-            if(size < 1024) {
-                return String.valueOf(size)+mBStr;
-            }
-            long kb, mb, rem;
-            kb = size >> 10;
-            rem = size - (kb << 10);
-            if(kb < 1024) {
-                if(rem > 512) {
-                    kb++;
-                }
-                retStr += String.valueOf(kb)+mKbStr;
-                return retStr;
-            }
-            mb = kb >> 10;
-            if(kb >= 512) {
-                //round off
-                mb++;
-            }
-            retStr += String.valueOf(mb)+ mMbStr;
-            return retStr;
-        } else {
-            Log.w(TAG, "Something fishy, cannot find size info for package:"+ps.packageName);
-        }
-        return retStr;
-    }
-    
-    public void sortAppList() {
-        // Sort application list
-        if(mSortOrder == SORT_ORDER_ALPHA) {
-            Collections.sort(mAppList, new ApplicationInfo.DisplayNameComparator(mPm));
-        } else if(mSortOrder == SORT_ORDER_SIZE) {
-            Collections.sort(mAppList, new AppInfoComparator(mSizeMap));
-        }
-    }
-    
-    private Map<String, Object> createMapEntry(CharSequence appName, 
-            Drawable appIcon, CharSequence appDesc, String sizeStr) {
-        Map<String, Object> map = new TreeMap<String, Object>();
-        map.put(KEY_APP_NAME, appName);
-        //the icon cannot be null. if the application hasnt set it, the default icon is returned.
-        map.put(KEY_APP_ICON, appIcon);
-        if(appDesc == null) {
-            appDesc="";
-        }
-        map.put(KEY_APP_DESC, appDesc);
-        map.put(KEY_APP_SIZE, sizeStr);
-        return map;
-    }
-    
+    // utility method used to start sub activity
     private void startApplicationDetailsActivity(ApplicationInfo info, PackageStats ps) {
-        //Create intent to start new activity
+        // Create intent to start new activity
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setClass(this, InstalledAppDetails.class);
-        intent.putExtra(APP_PKG_NAME, info.packageName);
-        if(localLOGV) Log.i(TAG, "code="+ps.codeSize+", cache="+ps.cacheSize+", data="+ps.dataSize);
-        intent.putExtra(APP_PKG_SIZE,  ps);
-        if(localLOGV) Log.i(TAG, "Starting sub activity to display info for app:"+info
-                +" with intent:"+intent);
-        //start new activity to display extended information
-        if ((info.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
-        }
+        mCurrentPkgName = info.packageName;
+        intent.putExtra(APP_PKG_NAME, mCurrentPkgName);
+        intent.putExtra(APP_PKG_SIZE, ps);
+        // start new activity to display extended information
         startActivityForResult(intent, INSTALLED_APP_DETAILS);
     }
     
-    public boolean setViewValue(View view, Object data, String textRepresentation) {
-        if(data == null) {
-            return false;
-        }
-        int id = view.getId();
-        switch(id) {
-        case R.id.app_name:
-            ((TextView)view).setText((String)data);
-            break;
-        case R.id.app_icon:
-            ((ImageView)view).setImageDrawable((Drawable)data);
-            break;
-        case R.id.app_description:
-            ((TextView)view).setText((String)data);
-            break;
-        case R.id.app_size:
-            ((TextView)view).setText((String)data);
-            break;
-        default:
-                break;
-        }
-        return true;
-    }
-    
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
-        menu.add(0, SORT_ORDER_ALPHA, 0, R.string.sort_order_alpha)
+        menu.add(0, SORT_ORDER_ALPHA, 1, R.string.sort_order_alpha)
                 .setIcon(android.R.drawable.ic_menu_sort_alphabetically);
-        menu.add(0, SORT_ORDER_SIZE, 0, R.string.sort_order_size)
-                .setIcon(android.R.drawable.ic_menu_sort_by_size);
+        menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size)
+                .setIcon(android.R.drawable.ic_menu_sort_by_size); 
+        menu.add(0, FILTER_APPS_ALL, 3, R.string.filter_apps_all);
+        menu.add(0, FILTER_APPS_RUNNING, 4, R.string.filter_apps_running);
+        menu.add(0, FILTER_APPS_THIRD_PARTY, 5, R.string.filter_apps_third_party);        
         return true;
     }
     
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
-        if(mDoneIniting) {
+        if (mDoneIniting) {
             menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA);
-            menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder!= SORT_ORDER_SIZE);
+            menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE);
+            menu.findItem(FILTER_APPS_ALL).setVisible(mFilterApps != FILTER_APPS_ALL);
+            menu.findItem(FILTER_APPS_THIRD_PARTY).setVisible(
+                    mFilterApps != FILTER_APPS_THIRD_PARTY);
+            menu.findItem(FILTER_APPS_RUNNING).setVisible(
+                    mFilterApps != FILTER_APPS_RUNNING);
             return true;
         } 
         return false;
@@ -607,10 +1153,13 @@
 
     public void onItemClick(AdapterView<?> parent, View view, int position,
             long id) {
-        mCurrentPkgIdx=position;
-        ApplicationInfo info = mAppList.get(position);
-        mCurrentPkg = info;        
-        PackageStats ps = mSizeMap.get(info.packageName);
-        startApplicationDetailsActivity(info, ps);
+        ApplicationInfo info = (ApplicationInfo)mAppInfoAdapter.getItem(position);
+        startApplicationDetailsActivity(info, mAppInfoAdapter.getAppStats(info.packageName));
+    }
+    
+    // onCancel call back for dialog thats displayed when data is being loaded
+    public void onCancel(DialogInterface dialog) {
+        mLoadingDlg = null;
+        finish();
     }
 }
diff --git a/src/com/android/settings/ProxySelector.java b/src/com/android/settings/ProxySelector.java
index d320e73..80fe3c9 100644
--- a/src/com/android/settings/ProxySelector.java
+++ b/src/com/android/settings/ProxySelector.java
@@ -222,7 +222,7 @@
         if (!TextUtils.isEmpty(hostname)) {
             hostname += ':' + portStr;
         }
-        Settings.System.putString(res, Settings.System.HTTP_PROXY, hostname);
+        Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY, hostname);
         sendBroadcast(new Intent(Proxy.PROXY_CHANGE_ACTION));
 
         return true;
diff --git a/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java
index ad30de7..b1ad777 100644
--- a/src/com/android/settings/RadioInfo.java
+++ b/src/com/android/settings/RadioInfo.java
@@ -31,13 +31,14 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.pim.DateUtils;
 import android.preference.PreferenceManager;
 import android.telephony.CellLocation;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.NeighboringCellInfo;
 import android.telephony.gsm.GsmCellLocation;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -66,6 +67,7 @@
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.List;
 
 public class RadioInfo extends Activity {
@@ -210,7 +212,7 @@
                 case EVENT_QUERY_NEIGHBORING_CIDS_DONE:
                     ar= (AsyncResult) msg.obj;
                     if (ar.exception == null) {
-                        updateNeighboringCids((String[])ar.result);
+                        updateNeighboringCids((ArrayList<NeighboringCellInfo>)ar.result);
                     } else {
                         mNeighboringCids.setText("unknown");
                     }
@@ -651,23 +653,21 @@
                 + ((cid == -1) ? "unknown" : Integer.toHexString(cid)));
     }
 
-    private final void updateNeighboringCids(String[] cids) {
-        if (cids != null && cids.length > 0 && cids[0] != null) {
-            int size = Integer.parseInt(cids[0]);
-            String neiborings;
-            if (size > 0) {
-                neiborings = "{";
-                for (int i=1; i<=size; i++) {
-                    neiborings += cids[i] + ", ";
-                }
-                neiborings += "}";
+    private final void updateNeighboringCids(ArrayList<NeighboringCellInfo> cids) {
+        String neighborings = "";
+        if (cids != null) {
+            if ( cids.isEmpty() ) {
+                neighborings = "no neighboring cells";
             } else {
-                neiborings = "none";
+                for (NeighboringCellInfo cell : cids) {
+                    neighborings += "{" + Integer.toHexString(cell.getCid()) 
+                    + "@" + cell.getRssi() + "} ";
+                }
             }
-            mNeighboringCids.setText(neiborings);
         } else {
-            mNeighboringCids.setText("unknown");
+            neighborings = "unknown";
         }
+        mNeighboringCids.setText(neighborings);
     }
 
     private final void
@@ -952,13 +952,15 @@
                   .append("\n    to ")
                   .append(pdp.getApn().toString())
                   .append("\ninterface: ")
-                  .append(phone.getInterfaceName(phone.getActiveApn()))
+                  .append(phone.getInterfaceName(phone.getActiveApnTypes()[0]))
                   .append("\naddress: ")
-                  .append(phone.getIpAddress(phone.getActiveApn()))
+                  .append(phone.getIpAddress(phone.getActiveApnTypes()[0]))
                   .append("\ngateway: ")
-                  .append(phone.getGateway(phone.getActiveApn()));
-                String[] dns = phone.getDnsServers(phone.getActiveApn()); 
-                sb.append("\ndns: ").append(dns[0]).append(", ").append(dns[1]);
+                  .append(phone.getGateway(phone.getActiveApnTypes()[0]));
+                String[] dns = phone.getDnsServers(phone.getActiveApnTypes()[0]);
+                if (dns != null) {
+                    sb.append("\ndns: ").append(dns[0]).append(", ").append(dns[1]);
+                }
             } else if (pdp.getState().isInactive()) {
                 sb.append("    disconnected with last try at ")
                   .append(DateUtils.timeString(pdp.getLastFailTime()))
diff --git a/src/com/android/settings/RingerVolumePreference.java b/src/com/android/settings/RingerVolumePreference.java
new file mode 100644
index 0000000..2d21ec6
--- /dev/null
+++ b/src/com/android/settings/RingerVolumePreference.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2008 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.settings;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.preference.VolumePreference;
+import android.preference.VolumePreference.SeekBarVolumizer;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+/**
+ * Special preference type that allows configuration of both the ring volume and
+ * notification volume.
+ */
+public class RingerVolumePreference extends VolumePreference implements
+        CheckBox.OnCheckedChangeListener {
+    private static final String TAG = "RingerVolumePreference";
+
+    private CheckBox mNotificationsUseRingVolumeCheckbox;
+    private SeekBarVolumizer mNotificationSeekBarVolumizer;
+    private TextView mNotificationVolumeTitle;
+    
+    public RingerVolumePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // The always visible seekbar is for ring volume
+        setStreamType(AudioManager.STREAM_RING);
+        
+        setDialogLayoutResource(R.layout.preference_dialog_ringervolume);
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+     
+        mNotificationsUseRingVolumeCheckbox =
+                (CheckBox) view.findViewById(R.id.same_notification_volume);
+        mNotificationsUseRingVolumeCheckbox.setOnCheckedChangeListener(this);
+        mNotificationsUseRingVolumeCheckbox.setChecked(Settings.System.getInt(
+                getContext().getContentResolver(),
+                Settings.System.NOTIFICATIONS_USE_RING_VOLUME, 1) == 1);
+        
+        final SeekBar seekBar = (SeekBar) view.findViewById(R.id.notification_volume_seekbar);
+        mNotificationSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar,
+                AudioManager.STREAM_NOTIFICATION);
+        
+        mNotificationVolumeTitle = (TextView) view.findViewById(R.id.notification_volume_title);
+        
+        setNotificationVolumeVisibility(!mNotificationsUseRingVolumeCheckbox.isChecked());
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        
+        if (!positiveResult && mNotificationSeekBarVolumizer != null) {
+            mNotificationSeekBarVolumizer.revertVolume();
+        }
+        
+        cleanup();
+    }
+
+    @Override
+    public void onActivityStop() {
+        super.onActivityStop();
+        cleanup();
+    }
+    
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        setNotificationVolumeVisibility(!isChecked);
+        
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATIONS_USE_RING_VOLUME, isChecked ? 1 : 0);
+        
+        if (isChecked) {
+            // The user wants the notification to be same as ring, so do a
+            // one-time sync right now
+            AudioManager audioManager = (AudioManager) getContext()
+                    .getSystemService(Context.AUDIO_SERVICE);
+            audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION,
+                    audioManager.getStreamVolume(AudioManager.STREAM_RING), 0);
+        }
+    }
+
+    @Override
+    protected void onSampleStarting(SeekBarVolumizer volumizer) {
+        super.onSampleStarting(volumizer);
+        
+        if (mNotificationSeekBarVolumizer != null && volumizer != mNotificationSeekBarVolumizer) {
+            mNotificationSeekBarVolumizer.stopSample();
+        }
+    }
+
+    private void setNotificationVolumeVisibility(boolean visible) {
+        if (mNotificationSeekBarVolumizer != null) {
+            mNotificationSeekBarVolumizer.getSeekBar().setVisibility(
+                    visible ? View.VISIBLE : View.GONE);
+            mNotificationVolumeTitle.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
+    
+    private void cleanup() {
+        if (mNotificationSeekBarVolumizer != null) {
+            mNotificationSeekBarVolumizer.stop();
+            mNotificationSeekBarVolumizer = null;
+        }
+    }
+    
+}
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index c1a509a..a0a52a2 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -44,11 +44,13 @@
     
     private static final String KEY_LOCK_ENABLED = "lockenabled";
     private static final String KEY_VISIBLE_PATTERN = "visiblepattern";
+    private static final String KEY_TACTILE_FEEDBACK_ENABLED = "tactilefeedback";
     private static final int CONFIRM_PATTERN_REQUEST_CODE = 55;
 
     private LockPatternUtils mLockPatternUtils;
     private CheckBoxPreference mLockEnabled;
     private CheckBoxPreference mVisiblePattern;
+    private CheckBoxPreference mTactileFeedback;
     private Preference mChoosePattern;
 
     private CheckBoxPreference mShowPassword;
@@ -103,6 +105,12 @@
         mVisiblePattern.setTitle(R.string.lockpattern_settings_enable_visible_pattern_title);
         inlinePrefCat.addPreference(mVisiblePattern);
 
+        // tactile feedback
+        mTactileFeedback = new CheckBoxPreference(this);
+        mTactileFeedback.setKey(KEY_TACTILE_FEEDBACK_ENABLED);
+        mTactileFeedback.setTitle(R.string.lockpattern_settings_enable_tactile_feedback_title);
+        inlinePrefCat.addPreference(mTactileFeedback);
+
         // change pattern lock
         Intent intent = new Intent();
         intent.setClassName("com.android.settings",
@@ -146,9 +154,11 @@
         boolean patternExists = mLockPatternUtils.savedPatternExists();
         mLockEnabled.setEnabled(patternExists);
         mVisiblePattern.setEnabled(patternExists);
+        mTactileFeedback.setEnabled(patternExists);
 
         mLockEnabled.setChecked(mLockPatternUtils.isLockPatternEnabled());
         mVisiblePattern.setChecked(mLockPatternUtils.isVisiblePatternEnabled());
+        mTactileFeedback.setChecked(mLockPatternUtils.isTactileFeedbackEnabled());
 
         int chooseStringRes = mLockPatternUtils.savedPatternExists() ?
                 R.string.lockpattern_settings_change_lock_pattern :
@@ -169,6 +179,8 @@
             mLockPatternUtils.setLockPatternEnabled(isToggled(preference));
         } else if (KEY_VISIBLE_PATTERN.equals(key)) {
             mLockPatternUtils.setVisiblePatternEnabled(isToggled(preference));
+        } else if (KEY_TACTILE_FEEDBACK_ENABLED.equals(key)) {
+            mLockPatternUtils.setTactileFeedbackEnabled(isToggled(preference));
         } else if (preference == mShowPassword) {
             Settings.System.putInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD,
                     mShowPassword.isChecked() ? 1 : 0);
@@ -198,9 +210,9 @@
     }
 
     private void setProviders(String providers) {
-        // Update the system setting LOCATION_PROVIDERS_ALLOWED
-        Settings.System.putString(getContentResolver(),
-                Settings.System.LOCATION_PROVIDERS_ALLOWED, providers);
+        // Update the secure setting LOCATION_PROVIDERS_ALLOWED
+        Settings.Secure.putString(getContentResolver(),
+            Settings.Secure.LOCATION_PROVIDERS_ALLOWED, providers);
         if (Config.LOGV) {
             Log.v("Location Accuracy", "Setting LOCATION_PROVIDERS_ALLOWED = " + providers);
         }
@@ -213,8 +225,8 @@
      */
     private String getAllowedProviders() {
         String allowedProviders =
-            Settings.System.getString(getContentResolver(),
-                Settings.System.LOCATION_PROVIDERS_ALLOWED);
+            Settings.Secure.getString(getContentResolver(),
+                Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
         if (allowedProviders == null) {
             allowedProviders = "";
         }
diff --git a/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java
index 82eadca..c40dd07 100644
--- a/src/com/android/settings/SettingsLicenseActivity.java
+++ b/src/com/android/settings/SettingsLicenseActivity.java
@@ -28,13 +28,10 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
 
-import org.apache.commons.codec.binary.Base64;
-
 /**
  * The "dialog" that shows from "License" in the Settings app.
  */
@@ -92,27 +89,8 @@
         
         WebView webView = new WebView(this);
 
-        if (LOGV) Log.v(TAG, "Started encode at " + System.currentTimeMillis());
-        // Need to encode to base64 for WebView to load the contents properly
-        String dataStr;
-        try {
-            byte[] base64Bytes = Base64.encodeBase64(data.toString().getBytes("ISO8859_1"));
-            dataStr = new String(base64Bytes);
-        } catch (UnsupportedEncodingException e) {
-            Log.e(TAG, "Could not convert to base64", e);
-            showErrorAndFinish();
-            return;
-        }
-        if (LOGV) Log.v(TAG, "Ended encode at " + System.currentTimeMillis());
-        if (LOGV) {
-            Log.v(TAG, "Started test decode at " + System.currentTimeMillis());
-            Base64.decodeBase64(dataStr.getBytes());
-            Log.v(TAG, "Ended decode at " + System.currentTimeMillis());
-        }
-
-        
         // Begin the loading.  This will be done in a separate thread in WebView.
-        webView.loadData(dataStr, "text/html", "base64");
+        webView.loadDataWithBaseURL(null, data.toString(), "text/html", "utf-8", null);
         webView.setWebViewClient(new WebViewClient() {
             @Override
             public void onPageFinished(WebView view, String url) {
diff --git a/src/com/android/settings/SoundAndDisplaySettings.java b/src/com/android/settings/SoundAndDisplaySettings.java
index 887fb8f..134e84f 100644
--- a/src/com/android/settings/SoundAndDisplaySettings.java
+++ b/src/com/android/settings/SoundAndDisplaySettings.java
@@ -25,6 +25,8 @@
 import android.content.IntentFilter;
 import android.media.AudioManager;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
@@ -32,6 +34,7 @@
 import android.preference.CheckBoxPreference;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.IWindowManager;
 
 public class SoundAndDisplaySettings extends PreferenceActivity implements
         Preference.OnPreferenceChangeListener {
@@ -45,14 +48,19 @@
     private static final String KEY_SCREEN_TIMEOUT = "screen_timeout";
     private static final String KEY_DTMF_TONE = "dtmf_tone";
     private static final String KEY_SOUND_EFFECTS = "sound_effects";
+    private static final String KEY_ANIMATIONS = "animations";
     
     private CheckBoxPreference mSilent;
     private CheckBoxPreference mVibrate;
     private CheckBoxPreference mDtmfTone;
     private CheckBoxPreference mSoundEffects;
+    private CheckBoxPreference mAnimations;
+    private float[] mAnimationScales;
     
     private AudioManager mAudioManager;
     
+    private IWindowManager mWindowManager;
+
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -73,6 +81,7 @@
         ContentResolver resolver = getContentResolver();
         
         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
         
         addPreferencesFromResource(R.xml.sound_and_display_settings);
         
@@ -86,11 +95,13 @@
         mSoundEffects.setPersistent(false);
         mSoundEffects.setChecked(Settings.System.getInt(resolver,
                 Settings.System.SOUND_EFFECTS_ENABLED, 0) != 0);
+        mAnimations = (CheckBoxPreference) findPreference(KEY_ANIMATIONS);
+        mAnimations.setPersistent(false);
         
         ListPreference screenTimeoutPreference =
             (ListPreference) findPreference(KEY_SCREEN_TIMEOUT);
         screenTimeoutPreference.setValue(String.valueOf(Settings.System.getInt(
-                getContentResolver(), SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE)));
+                resolver, SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE)));
         screenTimeoutPreference.setOnPreferenceChangeListener(this);
     }
     
@@ -124,6 +135,23 @@
         if (phoneVibrate != mVibrate.isChecked() || force) {
             mVibrate.setChecked(phoneVibrate);
         }
+        
+        boolean animations = true;
+        try {
+            mAnimationScales = mWindowManager.getAnimationScales();
+        } catch (RemoteException e) {
+        }
+        if (mAnimationScales != null) {
+            for (int i=0; i<mAnimationScales.length; i++) {
+                if (mAnimationScales[i] == 0) {
+                    animations = false;
+                    break;
+                }
+            }
+        }
+        if (animations != mAnimations.isChecked() || force) {
+            mAnimations.setChecked(animations);
+        }
     }
 
     @Override
@@ -151,6 +179,15 @@
             }
             Settings.System.putInt(getContentResolver(), Settings.System.SOUND_EFFECTS_ENABLED,
                     mSoundEffects.isChecked() ? 1 : 0);
+            
+        } else if (preference == mAnimations) {
+            for (int i=0; i<mAnimationScales.length; i++) {
+                mAnimationScales[i] = mAnimations.isChecked() ? 1 : 0;
+            }
+            try {
+                mWindowManager.setAnimationScales(mAnimationScales);
+            } catch (RemoteException e) {
+            }
         }
         return true;
     }
diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java
index 18b30bd..d112915 100644
--- a/src/com/android/settings/WirelessSettings.java
+++ b/src/com/android/settings/WirelessSettings.java
@@ -16,25 +16,13 @@
 
 package com.android.settings;
 
+import com.android.settings.bluetooth.BluetoothEnabler;
 import com.android.settings.wifi.WifiEnabler;
 
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothIntent;
-import android.bluetooth.IBluetoothDeviceCallback;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.preference.Preference;
 import android.preference.PreferenceActivity;
-import android.preference.PreferenceScreen;
 import android.preference.CheckBoxPreference;
-import android.provider.Settings;
-import android.widget.Toast;
 
 public class WirelessSettings extends PreferenceActivity {
 
@@ -44,13 +32,7 @@
 
     private WifiEnabler mWifiEnabler;
     private AirplaneModeEnabler mAirplaneModeEnabler;
-    
-    private CheckBoxPreference mToggleBluetooth;
-    
-    private IntentFilter mIntentFilter;
-    
-    private static final int EVENT_FAILED_BT_ENABLE = 1;
-    private static final int EVENT_PASSED_BT_ENABLE = 2;
+    private BluetoothEnabler mBtEnabler;
     
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -58,30 +40,25 @@
 
         addPreferencesFromResource(R.xml.wireless_settings);
 
-        mIntentFilter = new IntentFilter();
-        mIntentFilter.addAction(BluetoothIntent.ENABLED_ACTION);
-        mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
-
         initToggles();
     }
     
     @Override
     protected void onResume() {
         super.onResume();
-        refreshToggles();
-        registerReceiver(mReceiver, mIntentFilter);       
         
         mWifiEnabler.resume();
         mAirplaneModeEnabler.resume();
+        mBtEnabler.resume();
     }
     
     @Override
     protected void onPause() {
         super.onPause();
-        unregisterReceiver(mReceiver);
-
+        
         mWifiEnabler.pause();
         mAirplaneModeEnabler.pause();
+        mBtEnabler.pause();
     }
     
     private void initToggles() {
@@ -95,116 +72,9 @@
                 this,
                 (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE));
         
-        mToggleBluetooth = (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH);
-        mToggleBluetooth.setPersistent(false);
+        mBtEnabler = new BluetoothEnabler(
+                this,
+                (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH));
     }
     
-    private void refreshToggles() {
-        mToggleBluetooth.setChecked(isBluetoothEnabled());
-        mToggleBluetooth.setEnabled(true);
-    }
-    
-    @Override
-    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
-        if (preference == mToggleBluetooth) {
-            setBluetoothEnabled(mToggleBluetooth.isChecked());
-            return true;
-        }
-        
-        return false;
-    }
-
-    private boolean isBluetoothEnabled() {
-        BluetoothDevice device = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
-        if (device != null) {
-            return device.isEnabled();
-        } else {
-            return false;
-        }
-    }
-    
-    private void setBluetoothEnabled(boolean enabled) {
-        try {
-            BluetoothDevice device = (BluetoothDevice)getSystemService(BLUETOOTH_SERVICE);
-            if (enabled) {
-                // Turn it off until intent or callback is delivered
-                mToggleBluetooth.setChecked(false);
-                if (device.enable(mBtCallback)) {
-                    mToggleBluetooth.setSummary(R.string.bluetooth_enabling);
-                    mToggleBluetooth.setEnabled(false);
-                }
-            } else {
-                if (device.disable()) {
-                    Settings.System.putInt(getContentResolver(),
-                            Settings.System.BLUETOOTH_ON, 0);
-                } else {
-                    // Unusual situation, that you can't turn off bluetooth
-                    mToggleBluetooth.setChecked(true);
-                }
-            }
-        } catch (NullPointerException e) {
-            // TODO: 1071858
-            mToggleBluetooth.setChecked(false);
-            mToggleBluetooth.setEnabled(false);
-        }
-    }
-
-    private IBluetoothDeviceCallback mBtCallback = new IBluetoothDeviceCallback.Stub() {
-        
-        public void onEnableResult(int res) {
-            switch (res) {
-            case BluetoothDevice.RESULT_FAILURE:
-                mHandler.sendMessage(mHandler.obtainMessage(EVENT_FAILED_BT_ENABLE, 0));
-                break;
-            case BluetoothDevice.RESULT_SUCCESS:
-                mHandler.sendMessage(mHandler.obtainMessage(EVENT_PASSED_BT_ENABLE, 0));
-                break;
-            }
-        }
-        
-        public void onCreateBondingResult(String device, int res) {
-            // Don't care
-        }
-        public void onGetRemoteServiceChannelResult(String address, int channel) { }
-    };
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
-                updateBtStatus(true);
-            } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
-                mToggleBluetooth.setChecked(false);
-            }
-        }
-    };
-
-    private void updateBtStatus(boolean enabled) {
-        mToggleBluetooth.setChecked(enabled);
-        mToggleBluetooth.setEnabled(true);
-        mToggleBluetooth.setSummary(R.string.bluetooth_quick_toggle_summary);
-        if (enabled) {
-            Settings.System.putInt(getContentResolver(),
-                Settings.System.BLUETOOTH_ON, 1);
-        }
-    }
-    
-    private Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case EVENT_PASSED_BT_ENABLE:
-                    updateBtStatus(true);
-                    break;
-                case EVENT_FAILED_BT_ENABLE:
-                    updateBtStatus(false);
-                    Toast.makeText(WirelessSettings.this, 
-                            getResources().getString(R.string.bluetooth_failed_to_enable),
-                            Toast.LENGTH_SHORT).show();
-
-                    break;
-            }
-        }
-    };
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
new file mode 100644
index 0000000..f0a8189
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * BluetoothDevicePreference is the preference type used to display each remote
+ * Bluetooth device in the Bluetooth Settings screen.
+ */
+public class BluetoothDevicePreference extends Preference implements LocalBluetoothDevice.Callback {
+    private static final String TAG = "BluetoothDevicePreference";
+
+    private static int sDimAlpha = Integer.MIN_VALUE;
+    
+    private LocalBluetoothDevice mLocalDevice;
+    
+    /**
+     * Cached local copy of whether the device is busy. This is only updated
+     * from {@link #onDeviceAttributesChanged(LocalBluetoothDevice)}.
+     */ 
+    private boolean mIsBusy;
+    
+    public BluetoothDevicePreference(Context context, LocalBluetoothDevice localDevice) {
+        super(context);
+
+        if (sDimAlpha == Integer.MIN_VALUE) {
+            TypedValue outValue = new TypedValue();
+            context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
+            sDimAlpha = (int) (outValue.getFloat() * 255);
+        }
+            
+        mLocalDevice = localDevice;
+        
+        setLayoutResource(R.layout.preference_bluetooth);
+        
+        localDevice.registerCallback(this);
+        
+        onDeviceAttributesChanged(localDevice);
+    }
+    
+    public LocalBluetoothDevice getDevice() {
+        return mLocalDevice;
+    }
+
+    @Override
+    protected void onPrepareForRemoval() {
+        super.onPrepareForRemoval();
+        mLocalDevice.unregisterCallback(this);
+    }
+
+    public void onDeviceAttributesChanged(LocalBluetoothDevice device) {
+
+        /*
+         * The preference framework takes care of making sure the value has
+         * changed before proceeding.
+         */
+        
+        setTitle(mLocalDevice.getName());
+        
+        /*
+         * TODO: Showed "Paired" even though it was "Connected". This may be
+         * related to BluetoothHeadset not bound to the actual
+         * BluetoothHeadsetService when we got here.
+         */
+        setSummary(mLocalDevice.getSummary());
+
+        // Used to gray out the item
+        mIsBusy = mLocalDevice.isBusy();
+        
+        // Data has changed
+        notifyChanged();
+        
+        // This could affect ordering, so notify that also
+        notifyHierarchyChanged();
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return super.isEnabled() && !mIsBusy;
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        ImageView btClass = (ImageView) view.findViewById(R.id.btClass);
+        btClass.setImageResource(mLocalDevice.getBtClassDrawable());
+        btClass.setAlpha(isEnabled() ? 255 : sDimAlpha);        
+    }
+
+    @Override
+    public int compareTo(Preference another) {
+        if (!(another instanceof BluetoothDevicePreference)) {
+            // Put other preference types above us
+            return 1;
+        }
+        
+        return mLocalDevice.compareTo(((BluetoothDevicePreference) another).mLocalDevice);
+    }
+ 
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
new file mode 100644
index 0000000..f895696
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.preference.CheckBoxPreference;
+import android.util.Log;
+
+/**
+ * BluetoothDiscoverableEnabler is a helper to manage the "Discoverable"
+ * checkbox. It sets/unsets discoverability and keeps track of how much time
+ * until the the discoverability is automatically turned off.
+ */
+public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChangeListener {
+    private static final String TAG = "BluetoothDiscoverableEnabler";
+    private static final boolean V = LocalBluetoothManager.V;
+    
+    private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT =
+            "debug.bt.discoverable_time";
+    private static final int DISCOVERABLE_TIMEOUT = 120; 
+
+    private static final String SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP =
+            "discoverable_end_timestamp";
+    
+    private final Context mContext;
+    private final Handler mUiHandler;
+    private final CheckBoxPreference mCheckBoxPreference;
+    
+    private final LocalBluetoothManager mLocalManager;
+    
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            handleModeChanged(intent.getIntExtra(BluetoothIntent.MODE, 
+                    BluetoothDevice.MODE_UNKNOWN));
+        }
+    };
+
+    private final Runnable mUpdateCountdownSummaryRunnable = new Runnable() {
+        public void run() {
+            updateCountdownSummary();        
+        } 
+    };
+
+    public BluetoothDiscoverableEnabler(Context context, CheckBoxPreference checkBoxPreference) {
+        mContext = context;
+        mUiHandler = new Handler();
+        mCheckBoxPreference = checkBoxPreference;
+        
+        checkBoxPreference.setPersistent(false);
+        
+        mLocalManager = LocalBluetoothManager.getInstance(context);
+        if (mLocalManager == null) {
+            // Bluetooth not supported
+            checkBoxPreference.setEnabled(false);
+        }
+    }
+
+    public void resume() {
+        if (mLocalManager == null) {
+            return;
+        }
+        
+        mContext.registerReceiver(mReceiver, 
+                new IntentFilter(BluetoothIntent.MODE_CHANGED_ACTION));
+        mCheckBoxPreference.setOnPreferenceChangeListener(this);
+        
+        handleModeChanged(mLocalManager.getBluetoothManager().getMode());
+    }
+    
+    public void pause() {
+        if (mLocalManager == null) {
+            return;
+        }
+        
+        mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
+        mCheckBoxPreference.setOnPreferenceChangeListener(null);
+        mContext.unregisterReceiver(mReceiver);
+    }
+    
+    public boolean onPreferenceChange(Preference preference, Object value) {
+        if (V) {
+            Log.v(TAG, "Preference changed to " + value);
+        }
+        
+        // Turn on/off BT discoverability
+        setEnabled((Boolean) value);
+        
+        return true;
+    }
+    
+    private void setEnabled(final boolean enable) {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+        
+        if (enable) {
+
+            int timeout = getDiscoverableTimeout();
+            manager.setDiscoverableTimeout(timeout);
+            
+            long endTimestamp = System.currentTimeMillis() + timeout * 1000;
+            persistDiscoverableEndTimestamp(endTimestamp);
+            
+            manager.setMode(BluetoothDevice.MODE_DISCOVERABLE);
+            handleModeChanged(BluetoothDevice.MODE_DISCOVERABLE);            
+            
+        } else {
+            manager.setMode(BluetoothDevice.MODE_CONNECTABLE);
+        }
+    }
+
+    private int getDiscoverableTimeout() {
+        int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1);
+        if (timeout <= 0) {
+            timeout = DISCOVERABLE_TIMEOUT;
+        }
+        
+        return timeout;
+    }
+
+    private void persistDiscoverableEndTimestamp(long endTimestamp) {
+        SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit();
+        editor.putLong(SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp);
+        editor.commit();
+    }
+    
+    private void handleModeChanged(int mode) {
+        if (V) {
+            Log.v(TAG, "Got mode changed: " + mode);
+        }
+        
+        if (mode == BluetoothDevice.MODE_DISCOVERABLE) {
+            mCheckBoxPreference.setChecked(true);
+            updateCountdownSummary();
+            
+        } else {
+            mCheckBoxPreference.setChecked(false);
+        }
+    }
+    
+    private void updateCountdownSummary() {
+        int mode = mLocalManager.getBluetoothManager().getMode();
+        if (mode != BluetoothDevice.MODE_DISCOVERABLE) return;
+            
+        long currentTimestamp = System.currentTimeMillis();
+        long endTimestamp = mLocalManager.getSharedPreferences().getLong(
+                SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0);
+            
+        if (currentTimestamp > endTimestamp) {
+            // We're still in discoverable mode, but maybe there isn't a timeout.
+            mCheckBoxPreference.setSummaryOn(null);
+            return;
+        }
+        
+        String formattedTimeLeft = String.valueOf((endTimestamp - currentTimestamp) / 1000);
+        
+        mCheckBoxPreference.setSummaryOn(
+                mContext.getResources().getString(R.string.bluetooth_is_discoverable,
+                        formattedTimeLeft));
+        
+        synchronized (this) {
+            mUiHandler.removeCallbacks(mUpdateCountdownSummaryRunnable);
+            mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000);
+        }
+    }
+    
+        
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java
new file mode 100644
index 0000000..661700f
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.preference.Preference;
+import android.preference.CheckBoxPreference;
+import android.text.TextUtils;
+import android.util.Config;
+
+/**
+ * BluetoothEnabler is a helper to manage the Bluetooth on/off checkbox
+ * preference. It is turns on/off Bluetooth and ensures the summary of the
+ * preference reflects the current state.
+ */
+public class BluetoothEnabler implements Preference.OnPreferenceChangeListener {
+    
+    private static final boolean LOCAL_LOGD = Config.LOGD || false;
+    private static final String TAG = "BluetoothEnabler";
+    
+    private final Context mContext; 
+    private final CheckBoxPreference mCheckBoxPreference;
+    private final CharSequence mOriginalSummary;
+    
+    private final LocalBluetoothManager mLocalManager;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            handleStateChanged(mLocalManager.getBluetoothState());
+        }
+    };
+
+    public BluetoothEnabler(Context context, CheckBoxPreference checkBoxPreference) {
+        mContext = context;
+        mCheckBoxPreference = checkBoxPreference;
+        
+        mOriginalSummary = checkBoxPreference.getSummary();
+        checkBoxPreference.setPersistent(false);
+        
+        mLocalManager = LocalBluetoothManager.getInstance(context);
+        if (mLocalManager == null) {
+            // Bluetooth not supported
+            checkBoxPreference.setEnabled(false);
+        }
+    }
+
+    public void resume() {
+        if (mLocalManager == null) {
+            return;
+        }
+        
+        ExtendedBluetoothState state = mLocalManager.getBluetoothState();
+        // This is the widget enabled state, not the preference toggled state
+        mCheckBoxPreference.setEnabled(state == ExtendedBluetoothState.ENABLED ||
+                state == ExtendedBluetoothState.DISABLED);
+        // BT state is not a sticky broadcast, so set it manually
+        handleStateChanged(state);
+        
+        mContext.registerReceiver(mReceiver, 
+                new IntentFilter(LocalBluetoothManager.EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+        mCheckBoxPreference.setOnPreferenceChangeListener(this);
+    }
+    
+    public void pause() {
+        if (mLocalManager == null) {
+            return;
+        }
+        
+        mContext.unregisterReceiver(mReceiver);
+        mCheckBoxPreference.setOnPreferenceChangeListener(null);
+    }
+    
+    public boolean onPreferenceChange(Preference preference, Object value) {
+        // Turn on/off BT
+        setEnabled((Boolean) value);
+        
+        // Don't update UI to opposite state until we're sure
+        return false;
+    }
+    
+    private void setEnabled(final boolean enable) {
+        // Disable preference
+        mCheckBoxPreference.setEnabled(false);
+        
+        mLocalManager.setBluetoothEnabled(enable);
+    }
+    
+    private void handleStateChanged(ExtendedBluetoothState state) {
+
+        if (state == ExtendedBluetoothState.DISABLED || state == ExtendedBluetoothState.ENABLED) {
+            mCheckBoxPreference.setChecked(state == ExtendedBluetoothState.ENABLED);
+            mCheckBoxPreference
+                    .setSummary(state == ExtendedBluetoothState.DISABLED ? mOriginalSummary : null);
+            
+            mCheckBoxPreference.setEnabled(isEnabledByDependency());
+            
+        } else if (state == ExtendedBluetoothState.ENABLING ||
+                state == ExtendedBluetoothState.DISABLING) {
+            mCheckBoxPreference.setSummary(state == ExtendedBluetoothState.ENABLING
+                    ? R.string.wifi_starting
+                    : R.string.wifi_stopping);
+            
+        } else if (state == ExtendedBluetoothState.UNKNOWN) {
+            mCheckBoxPreference.setChecked(false);
+            mCheckBoxPreference.setSummary(R.string.wifi_error);
+            mCheckBoxPreference.setEnabled(true);
+        }
+    }
+
+    private boolean isEnabledByDependency() {
+        Preference dep = getDependencyPreference();
+        if (dep == null) {
+            return true;
+        }
+        
+        return !dep.shouldDisableDependents();
+    }
+    
+    private Preference getDependencyPreference() {
+        String depKey = mCheckBoxPreference.getDependency();
+        if (TextUtils.isEmpty(depKey)) {
+            return null;
+        }
+        
+        return mCheckBoxPreference.getPreferenceManager().findPreference(depKey);
+    }
+    
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
new file mode 100644
index 0000000..bcad206
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothIntent;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+/**
+ * BluetoothEventRedirector receives broadcasts and callbacks from the Bluetooth
+ * API and dispatches the event on the UI thread to the right class in the
+ * Settings.
+ */
+public class BluetoothEventRedirector {
+    private static final String TAG = "BluetoothEventRedirector";
+    private static final boolean V = LocalBluetoothManager.V;
+    
+    private LocalBluetoothManager mManager;
+    private Handler mUiHandler = new Handler();
+    
+    private IBluetoothDeviceCallback mBtDevCallback = new IBluetoothDeviceCallback.Stub() {
+        public void onCreateBondingResult(final String address, final int result) {
+            if (V) {
+                Log.v(TAG, "onCreateBondingResult(" + address + ", " + result + ")");
+            }
+            
+            mUiHandler.post(new Runnable() {
+                public void run() {
+                    boolean wasSuccess = result == BluetoothDevice.RESULT_SUCCESS; 
+                    LocalBluetoothDeviceManager deviceManager = mManager.getLocalDeviceManager();
+                    deviceManager.onBondingStateChanged(address, wasSuccess);
+                    if (!wasSuccess) {
+                        deviceManager.onBondingError(address);
+                    }
+                }
+            });
+        }
+
+        public void onEnableResult(int result) { }
+        public void onGetRemoteServiceChannelResult(String address, int channel) { }
+    };
+    
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (V) {
+                Log.v(TAG, "Received " + intent.getAction());
+            }
+            
+            String action = intent.getAction();
+            String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+                
+            if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
+                mManager.setBluetoothStateInt(ExtendedBluetoothState.ENABLED);
+            } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
+                mManager.setBluetoothStateInt(ExtendedBluetoothState.DISABLED);
+                    
+            } else if (action.equals(BluetoothIntent.DISCOVERY_STARTED_ACTION)) {
+                mManager.onScanningStateChanged(true);
+            } else if (action.equals(BluetoothIntent.DISCOVERY_COMPLETED_ACTION)) {
+                mManager.onScanningStateChanged(false);
+                    
+            } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)) {
+                short rssi = intent.getShortExtra(BluetoothIntent.RSSI, Short.MIN_VALUE);
+                mManager.getLocalDeviceManager().onDeviceAppeared(address, rssi);                    
+            } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION)) {
+                mManager.getLocalDeviceManager().onDeviceDisappeared(address);
+            } else if (action.equals(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION)) {
+                mManager.getLocalDeviceManager().onDeviceNameUpdated(address);
+                
+            } else if (action.equals(BluetoothIntent.BONDING_CREATED_ACTION)) {
+                mManager.getLocalDeviceManager().onBondingStateChanged(address, true);
+            } else if (action.equals(BluetoothIntent.BONDING_REMOVED_ACTION)) {
+                mManager.getLocalDeviceManager().onBondingStateChanged(address, false);
+                
+            } else if (action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION)) {
+                mManager.getLocalDeviceManager().onProfileStateChanged(address);
+
+                int newState = intent.getIntExtra(BluetoothIntent.HEADSET_STATE, 0);
+                int oldState = intent.getIntExtra(BluetoothIntent.HEADSET_PREVIOUS_STATE, 0);
+                if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
+                        oldState == BluetoothHeadset.STATE_CONNECTING) {
+                    mManager.getLocalDeviceManager().onConnectingError(address);
+                }
+                
+            } else if (action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) {
+                mManager.getLocalDeviceManager().onProfileStateChanged(address);
+
+                int newState = intent.getIntExtra(BluetoothA2dp.SINK_STATE, 0);
+                int oldState = intent.getIntExtra(BluetoothA2dp.SINK_PREVIOUS_STATE, 0);
+                if (newState == BluetoothA2dp.STATE_DISCONNECTED &&
+                        oldState == BluetoothA2dp.STATE_CONNECTING) {
+                    mManager.getLocalDeviceManager().onConnectingError(address);
+                }
+            }
+        }
+    };
+
+    public BluetoothEventRedirector(LocalBluetoothManager localBluetoothManager) {
+        mManager = localBluetoothManager;
+    }
+
+    public void start() {
+        IntentFilter filter = new IntentFilter();
+        
+        // Bluetooth on/off broadcasts
+        filter.addAction(BluetoothIntent.ENABLED_ACTION);
+        filter.addAction(BluetoothIntent.DISABLED_ACTION);
+        
+        // Discovery broadcasts
+        filter.addAction(BluetoothIntent.DISCOVERY_STARTED_ACTION);
+        filter.addAction(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
+        filter.addAction(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
+        filter.addAction(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION);
+        filter.addAction(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION);
+        
+        // Pairing broadcasts
+        filter.addAction(BluetoothIntent.BONDING_CREATED_ACTION);
+        filter.addAction(BluetoothIntent.BONDING_REMOVED_ACTION);
+        
+        // Fine-grained state broadcasts
+        filter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
+        filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
+        
+        mManager.getContext().registerReceiver(mBroadcastReceiver, filter);
+    }
+    
+    public void stop() {
+        mManager.getContext().unregisterReceiver(mBroadcastReceiver);   
+    }
+    
+    public IBluetoothDeviceCallback getBluetoothDeviceCallback() { 
+        return mBtDevCallback;
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothNamePreference.java b/src/com/android/settings/bluetooth/BluetoothNamePreference.java
new file mode 100644
index 0000000..3065b26
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothNamePreference.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.preference.EditTextPreference;
+import android.preference.PreferenceManager;
+import android.util.AttributeSet;
+
+/**
+ * BluetoothNamePreference is the preference type for editing the device's
+ * Bluetooth name. It asks the user for a name, and persists it via the
+ * Bluetooth API.
+ */
+public class BluetoothNamePreference extends EditTextPreference {
+    private static final String TAG = "BluetoothNamePreference";
+
+    private LocalBluetoothManager mLocalManager;
+    
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            setSummaryToName();
+        }
+    };
+    
+    public BluetoothNamePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        
+        mLocalManager = LocalBluetoothManager.getInstance(context);
+        
+        setSummaryToName();        
+    }
+
+    public void resume() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothIntent.ENABLED_ACTION);
+        filter.addAction(BluetoothIntent.NAME_CHANGED_ACTION);
+        getContext().registerReceiver(mReceiver, filter);
+    }
+    
+    public void pause() {
+        getContext().unregisterReceiver(mReceiver);
+    }
+    
+    private void setSummaryToName() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+        if (manager.isEnabled()) {
+            setSummary(manager.getName());
+        }
+    }
+
+    @Override
+    protected boolean persistString(String value) {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+        manager.setName(value);
+        return true;        
+    }
+    
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPinDialog.java b/src/com/android/settings/bluetooth/BluetoothPinDialog.java
new file mode 100644
index 0000000..291d0c1
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPinDialog.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputFilter;
+import android.text.method.DigitsKeyListener;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.settings.R;
+
+/**
+ * BluetoothPinDialog asks the user to enter a PIN for pairing with a remote
+ * Bluetooth device. It is an activity that appears as a dialog.
+ */
+public class BluetoothPinDialog extends AlertActivity implements DialogInterface.OnClickListener {
+    private static final String TAG = "BluetoothPinDialog";
+
+    private LocalBluetoothManager mLocalManager;
+    private String mAddress;
+    private EditText mPinView;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        if (!intent.getAction().equals(BluetoothIntent.PAIRING_REQUEST_ACTION))
+        {
+            Log.e(TAG,
+                  "Error: this activity may be started only with intent " +
+                  BluetoothIntent.PAIRING_REQUEST_ACTION);
+            finish();
+        }
+        
+        mLocalManager = LocalBluetoothManager.getInstance(this);
+        mAddress = intent.getStringExtra(BluetoothIntent.ADDRESS);
+        
+        // Set up the "dialog"
+        final AlertController.AlertParams p = mAlertParams;
+        p.mIconId = android.R.drawable.ic_dialog_info;
+        p.mTitle = getString(R.string.bluetooth_pin_entry);
+        p.mView = createView();
+        p.mPositiveButtonText = getString(android.R.string.ok);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = getString(android.R.string.cancel);
+        p.mNegativeButtonListener = this;
+        setupAlert();
+    }
+
+    private View createView() {
+        View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null);
+        
+        String name = mLocalManager.getLocalDeviceManager().getName(mAddress);
+        TextView messageView = (TextView) view.findViewById(R.id.message);
+        messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name));
+        
+        mPinView = (EditText) view.findViewById(R.id.text);
+        
+        return view;
+    }
+    
+    private void onPair(String pin) {
+        byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
+        
+        if (pinBytes == null) {
+            return;
+        }
+        
+        mLocalManager.getBluetoothManager().setPin(mAddress, pinBytes);
+    }
+
+    private void onCancel() {
+        mLocalManager.getBluetoothManager().cancelPin(mAddress);
+    }
+    
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case DialogInterface.BUTTON_POSITIVE:
+                onPair(mPinView.getText().toString());
+                break;
+                
+            case DialogInterface.BUTTON_NEGATIVE:
+                onCancel();
+                break;
+        }
+    }
+
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPinRequest.java b/src/com/android/settings/bluetooth/BluetoothPinRequest.java
new file mode 100644
index 0000000..619052d
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPinRequest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+/**
+ * BluetoothPinRequest is a receiver for any Bluetooth pairing PIN request. It
+ * checks if the Bluetooth Settings is currently visible and brings up the PIN
+ * entry dialog. Otherwise it puts a Notification in the status bar, which can
+ * be clicked to bring up the PIN entry dialog.
+ */
+public class BluetoothPinRequest extends BroadcastReceiver {
+
+    public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
+    
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (action.equals(BluetoothIntent.PAIRING_REQUEST_ACTION)) {
+
+            LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(context);        
+        
+            String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+            Intent pinIntent = new Intent();
+            pinIntent.setClass(context, BluetoothPinDialog.class);
+            pinIntent.putExtra(BluetoothIntent.ADDRESS, address); 
+            pinIntent.setAction(BluetoothIntent.PAIRING_REQUEST_ACTION);
+            pinIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            
+            if (localManager.getForegroundActivity() != null) {
+                // Since the BT-related activity is in the foreground, just open the dialog
+                context.startActivity(pinIntent);
+                
+            } else {
+                
+                // Put up a notification that leads to the dialog
+                Resources res = context.getResources();
+                Notification notification = new Notification(
+                        android.R.drawable.stat_sys_data_bluetooth,
+                        res.getString(R.string.bluetooth_notif_ticker),
+                        System.currentTimeMillis());
+
+                PendingIntent pending = PendingIntent.getActivity(context, 0, 
+                        pinIntent, PendingIntent.FLAG_ONE_SHOT);
+                
+                String name = intent.getStringExtra(BluetoothIntent.NAME);
+                if (TextUtils.isEmpty(name)) {
+                    name = localManager.getLocalDeviceManager().getName(address);
+                }
+                
+                notification.setLatestEventInfo(context, 
+                        res.getString(R.string.bluetooth_notif_title), 
+                        res.getString(R.string.bluetooth_notif_message) + name, 
+                        pending);
+                notification.flags |= Notification.FLAG_AUTO_CANCEL;
+                
+                NotificationManager manager = (NotificationManager) 
+                        context.getSystemService(Context.NOTIFICATION_SERVICE);
+                manager.notify(NOTIFICATION_ID, notification);
+            }
+            
+        } else if (action.equals(BluetoothIntent.PAIRING_CANCEL_ACTION)) {
+            
+            // Remove the notification
+            NotificationManager manager = (NotificationManager) context
+                    .getSystemService(Context.NOTIFICATION_SERVICE);
+            manager.cancel(NOTIFICATION_ID);
+        }
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
new file mode 100644
index 0000000..316e831
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.ProgressCategory;
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.ExtendedBluetoothState;
+
+import java.util.List;
+import java.util.WeakHashMap;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+/**
+ * BluetoothSettings is the Settings screen for Bluetooth configuration and
+ * connection management.
+ */
+public class BluetoothSettings extends PreferenceActivity
+        implements LocalBluetoothManager.Callback {
+
+    private static final String TAG = "BluetoothSettings";
+
+    private static final int MENU_SCAN = Menu.FIRST;
+    
+    private static final String KEY_BT_CHECKBOX = "bt_checkbox";
+    private static final String KEY_BT_DISCOVERABLE = "bt_discoverable";
+    private static final String KEY_BT_DEVICE_LIST = "bt_device_list";
+    private static final String KEY_BT_NAME = "bt_name";
+    private static final String KEY_BT_SCAN = "bt_scan";
+    
+    private LocalBluetoothManager mLocalManager;
+    
+    private BluetoothEnabler mEnabler;
+    private BluetoothDiscoverableEnabler mDiscoverableEnabler;
+    
+    private BluetoothNamePreference mNamePreference;
+    
+    private ProgressCategory mDeviceList;
+    
+    private WeakHashMap<LocalBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
+            new WeakHashMap<LocalBluetoothDevice, BluetoothDevicePreference>();
+    
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // TODO: put this in callback instead of receiving
+            onBluetoothStateChanged(mLocalManager.getBluetoothState());
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mLocalManager = LocalBluetoothManager.getInstance(this);
+        if (mLocalManager == null) finish();        
+        
+        addPreferencesFromResource(R.xml.bluetooth_settings);
+        
+        mEnabler = new BluetoothEnabler(
+                this,
+                (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX));
+        
+        mDiscoverableEnabler = new BluetoothDiscoverableEnabler(
+                this,
+                (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE));
+    
+        mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME);
+        
+        mDeviceList = (ProgressCategory) findPreference(KEY_BT_DEVICE_LIST);
+        
+        registerForContextMenu(getListView());
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        // Repopulate (which isn't too bad since it's cached in the settings
+        // bluetooth manager
+        mDevicePreferenceMap.clear();
+        mDeviceList.removeAll();
+        addDevices();
+
+        mEnabler.resume();
+        mDiscoverableEnabler.resume();
+        mNamePreference.resume();
+        mLocalManager.registerCallback(this);
+        
+        mLocalManager.startScanning(false);
+
+        registerReceiver(mReceiver, 
+                new IntentFilter(LocalBluetoothManager.EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+        
+        mLocalManager.setForegroundActivity(this);
+    }
+    
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        mLocalManager.setForegroundActivity(null);
+        
+        unregisterReceiver(mReceiver);
+        
+        mLocalManager.unregisterCallback(this);
+        mNamePreference.pause();
+        mDiscoverableEnabler.pause();
+        mEnabler.pause();
+    }
+
+    private void addDevices() {
+        List<LocalBluetoothDevice> devices = mLocalManager.getLocalDeviceManager().getDevicesCopy();
+        for (LocalBluetoothDevice device : devices) {
+            onDeviceAdded(device);
+        }
+    }
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        menu.add(0, MENU_SCAN, 0, R.string.bluetooth_scan_for_devices)
+                .setIcon(R.drawable.ic_menu_refresh)
+                .setAlphabeticShortcut('r');
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        menu.findItem(MENU_SCAN).setEnabled(mLocalManager.getBluetoothManager().isEnabled());
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        
+            case MENU_SCAN:
+                mLocalManager.startScanning(true);
+                return true;
+                
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+            Preference preference) {
+
+        if (KEY_BT_SCAN.equals(preference.getKey())) {
+            mLocalManager.startScanning(true);
+            return true;
+        }
+        
+        if (preference instanceof BluetoothDevicePreference) {
+            BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference;
+            btPreference.getDevice().onClicked();
+            return true;
+        }
+        
+        return super.onPreferenceTreeClick(preferenceScreen, preference);
+    }
+    
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v,
+            ContextMenuInfo menuInfo) {
+        LocalBluetoothDevice device = getDeviceFromMenuInfo(menuInfo);
+        if (device == null) return;
+        
+        device.onCreateContextMenu(menu);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        LocalBluetoothDevice device = getDeviceFromMenuInfo(item.getMenuInfo());
+        if (device == null) return false;
+        
+        device.onContextItemSelected(item);
+        return true;
+    }
+
+    private LocalBluetoothDevice getDeviceFromMenuInfo(ContextMenuInfo menuInfo) {
+        if ((menuInfo == null) || !(menuInfo instanceof AdapterContextMenuInfo)) {
+            return null;
+        }
+        
+        AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
+        Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(
+                adapterMenuInfo.position);
+        if (pref == null || !(pref instanceof BluetoothDevicePreference)) {
+            return null;
+        }
+
+        return ((BluetoothDevicePreference) pref).getDevice();
+    }
+    
+    public void onDeviceAdded(LocalBluetoothDevice device) {
+
+        if (mDevicePreferenceMap.get(device) != null) {
+            throw new IllegalStateException("Got onDeviceAdded, but device already exists");
+        }
+        
+        createDevicePreference(device);            
+    }
+
+    private void createDevicePreference(LocalBluetoothDevice device) {
+        BluetoothDevicePreference preference = new BluetoothDevicePreference(this, device);
+        mDeviceList.addPreference(preference);
+        mDevicePreferenceMap.put(device, preference);
+    }
+    
+    public void onDeviceDeleted(LocalBluetoothDevice device) {
+        BluetoothDevicePreference preference = mDevicePreferenceMap.remove(device);
+        if (preference != null) {
+            mDeviceList.removePreference(preference);
+        }
+    }
+
+    public void onScanningStateChanged(boolean started) {
+        mDeviceList.setProgress(started);
+    }
+    
+    private void onBluetoothStateChanged(ExtendedBluetoothState bluetoothState) {
+        // When bluetooth is enabled (and we are in the activity, which we are),
+        // we should start a scan
+        if (bluetoothState == ExtendedBluetoothState.ENABLED) {
+            mLocalManager.startScanning(false);
+        }
+    }
+}
diff --git a/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
new file mode 100644
index 0000000..f29ec79
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.util.Log;
+
+/**
+ * ConnectSpecificProfilesActivity presents the user with all of the profiles
+ * for a particular device, and allows him to choose which should be connected
+ * (or disconnected).
+ */
+public class ConnectSpecificProfilesActivity extends PreferenceActivity
+        implements LocalBluetoothDevice.Callback, Preference.OnPreferenceChangeListener {
+    private static final String TAG = "ConnectSpecificProfilesActivity";
+
+    private static final String KEY_ONLINE_MODE = "online_mode";
+    private static final String KEY_TITLE = "title";
+    private static final String KEY_PROFILE_CONTAINER = "profile_container";
+
+    public static final String EXTRA_ADDRESS = "address";
+    
+    private LocalBluetoothManager mManager;
+    private LocalBluetoothDevice mDevice;
+    
+    private PreferenceGroup mProfileContainer;
+    private CheckBoxPreference mOnlineModePreference;
+
+    /**
+     * The current mode of this activity and its checkboxes (either online mode
+     * or offline mode). In online mode, user interactions with the profile
+     * checkboxes will also toggle the profile's connectivity. In offline mode,
+     * they will not, and only the preferred state will be saved for the
+     * profile.
+     */
+    private boolean mOnlineMode;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        String address;
+        if (savedInstanceState != null) {
+            address = savedInstanceState.getString(EXTRA_ADDRESS);
+        } else {
+            Intent intent = getIntent();
+            address = intent.getStringExtra(EXTRA_ADDRESS);
+        }
+
+        if (TextUtils.isEmpty(address)) {
+            Log.w(TAG, "Activity started without address");
+            finish();
+        }
+        
+        mManager = LocalBluetoothManager.getInstance(this);
+        mDevice = mManager.getLocalDeviceManager().findDevice(address);
+        if (mDevice == null) {
+            Log.w(TAG, "Device not found, cannot connect to it");
+            finish();
+        }
+
+        addPreferencesFromResource(R.xml.bluetooth_device_advanced);
+        mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER);
+        
+        // Set the title of the screen
+        findPreference(KEY_TITLE).setTitle(
+                getString(R.string.bluetooth_device_advanced_title, mDevice.getName()));
+
+        // Listen for check/uncheck of the online mode checkbox
+        mOnlineModePreference = (CheckBoxPreference) findPreference(KEY_ONLINE_MODE);
+        mOnlineModePreference.setOnPreferenceChangeListener(this);
+        
+        // Add a preference for each profile
+        addPreferencesForProfiles();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        
+        outState.putString(EXTRA_ADDRESS, mDevice.getAddress());
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        mManager.setForegroundActivity(this);
+        mDevice.registerCallback(this);
+
+        refresh(true);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        
+        mDevice.unregisterCallback(this);
+        mManager.setForegroundActivity(null);
+    }
+
+    private void addPreferencesForProfiles() {
+        for (Profile profile : mDevice.getProfiles()) {
+            Preference pref = createProfilePreference(profile);
+            mProfileContainer.addPreference(pref);
+        }
+    }
+
+    /**
+     * Creates a checkbox preference for the particular profile. The key will be
+     * the profile's name.
+     * 
+     * @param profile The profile for which the preference controls.
+     * @return A preference that allows the user to choose whether this profile
+     *         will be connected to.
+     */
+    private CheckBoxPreference createProfilePreference(Profile profile) {
+        CheckBoxPreference pref = new CheckBoxPreference(this);
+        pref.setKey(profile.toString());
+        pref.setTitle(profile.localizedString);
+        pref.setPersistent(false);
+        pref.setOnPreferenceChangeListener(this);
+
+        refreshProfilePreference(pref, profile);
+        
+        return pref;
+    }
+
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        String key = preference.getKey();
+        if (TextUtils.isEmpty(key) || newValue == null) return true;
+        
+        if (key.equals(KEY_ONLINE_MODE)) {
+            onOnlineModeCheckedStateChanged((Boolean) newValue);
+            
+        } else {
+            Profile profile = getProfileOf(preference);
+            if (profile == null) return false;
+            onProfileCheckedStateChanged(profile, (Boolean) newValue);
+        }
+
+        return true;
+    }
+
+    private void onOnlineModeCheckedStateChanged(boolean checked) {
+        switchModes(checked, false);
+    }
+    
+    private void onProfileCheckedStateChanged(Profile profile, boolean checked) {
+        if (mOnlineMode) {
+            if (checked) {
+                mDevice.connect(profile);
+            } else {
+                mDevice.disconnect(profile);
+            }
+        }
+        
+        LocalBluetoothProfileManager.setPreferredProfile(this, mDevice.getAddress(), profile,
+                checked);
+    }
+    
+    public void onDeviceAttributesChanged(LocalBluetoothDevice device) {
+        refresh(false);
+    }
+
+    private void refresh(boolean forceRefresh) {
+        // The online mode could have changed
+        updateOnlineMode(forceRefresh);
+        refreshProfiles();
+        refreshOnlineModePreference();
+    }
+
+    private void updateOnlineMode(boolean force) {
+        // Connected or Connecting (and Disconnecting, which is fine)
+        boolean onlineMode = mDevice.isConnected() || mDevice.isBusy();
+        switchModes(onlineMode, force);
+    }
+    
+    /**
+     * Switches between online/offline mode.
+     * 
+     * @param onlineMode Whether to be in online mode, or offline mode.
+     */
+    private void switchModes(boolean onlineMode, boolean force) {
+        if (mOnlineMode != onlineMode || force) {
+            mOnlineMode = onlineMode;
+            
+            if (onlineMode) {
+                mDevice.connect();
+            } else {
+                mDevice.disconnect();
+            }
+
+            refreshOnlineModePreference();
+        }
+    }
+    
+    private void refreshOnlineModePreference() {
+        mOnlineModePreference.setChecked(mOnlineMode);
+
+        /**
+         * If the device is online, show status. Otherwise, show a summary that
+         * describes what the checkbox does.
+         */
+        mOnlineModePreference.setSummary(mOnlineMode ? mDevice.getSummary()
+                : R.string.bluetooth_device_advanced_online_mode_summary);
+    }
+    
+    private void refreshProfiles() {
+        for (Profile profile : mDevice.getProfiles()) {
+            CheckBoxPreference profilePref =
+                    (CheckBoxPreference) findPreference(profile.toString());
+            if (profilePref == null) continue;
+            
+            refreshProfilePreference(profilePref, profile);
+        }
+    }
+    
+    private void refreshProfilePreference(CheckBoxPreference profilePref, Profile profile) {
+        String address = mDevice.getAddress();
+        LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+                .getProfileManager(mManager, profile);
+        
+        int connectionStatus = profileManager.getConnectionStatus(address);
+        
+        profilePref.setSummary(getProfileSummary(profileManager, profile, address,
+                connectionStatus, mOnlineMode));
+        
+        profilePref.setChecked(
+                LocalBluetoothProfileManager.isPreferredProfile(this, address, profile));
+    }
+
+    private Profile getProfileOf(Preference pref) {
+        if (!(pref instanceof CheckBoxPreference)) return null;
+        String key = pref.getKey();
+        if (TextUtils.isEmpty(key)) return null;
+        
+        try {
+            return Profile.valueOf(pref.getKey());
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+    private static int getProfileSummary(LocalBluetoothProfileManager profileManager,
+            Profile profile, String address, int connectionStatus, boolean onlineMode) {
+        if (!onlineMode || connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED) {
+            return getProfileSummaryForSettingPreference(profile);
+        } else {
+            return profileManager.getSummary(address);
+        }
+    }
+    
+    /**
+     * Gets the summary that describes when checked, it will become a preferred profile.
+     * 
+     * @param profile The profile to get the summary for.
+     * @return The summary.
+     */
+    private static final int getProfileSummaryForSettingPreference(Profile profile) {
+        switch (profile) {
+            case A2DP:
+                return R.string.bluetooth_a2dp_profile_summary_use_for;
+            case HEADSET:
+                return R.string.bluetooth_headset_profile_summary_use_for;
+            default:
+                return 0;
+        }
+    }
+    
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothDevice.java b/src/com/android/settings/bluetooth/LocalBluetoothDevice.java
new file mode 100644
index 0000000..a8f79ff
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDevice.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.IBluetoothDeviceCallback;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LocalBluetoothDevice represents a remote Bluetooth device. It contains
+ * attributes of the device (such as the address, name, RSSI, etc.) and
+ * functionality that can be performed on the device (connect, pair, disconnect,
+ * etc.).
+ */
+public class LocalBluetoothDevice implements Comparable<LocalBluetoothDevice> {
+    private static final String TAG = "LocalBluetoothDevice";
+    
+    private static final int CONTEXT_ITEM_CONNECT = Menu.FIRST + 1;
+    private static final int CONTEXT_ITEM_DISCONNECT = Menu.FIRST + 2;
+    private static final int CONTEXT_ITEM_UNPAIR = Menu.FIRST + 3;
+    private static final int CONTEXT_ITEM_CONNECT_ADVANCED = Menu.FIRST + 4;
+    
+    private final String mAddress;
+    private String mName;
+    private short mRssi;
+    private int mBtClass = BluetoothClass.ERROR;
+    
+    private List<Profile> mProfiles = new ArrayList<Profile>();
+    
+    private boolean mVisible;
+    
+    private int mPairingStatus;
+    
+    private final LocalBluetoothManager mLocalManager;
+    
+    private List<Callback> mCallbacks = new ArrayList<Callback>();
+
+    /**
+     * When we connect to multiple profiles, we only want to display a single
+     * error even if they all fail. This tracks that state.
+     */
+    private boolean mIsConnectingErrorPossible;
+    
+    LocalBluetoothDevice(Context context, String address) {
+        mLocalManager = LocalBluetoothManager.getInstance(context);
+        if (mLocalManager == null) {
+            throw new IllegalStateException(
+                    "Cannot use LocalBluetoothDevice without Bluetooth hardware");
+        }
+        
+        mAddress = address;
+        
+        fillData();
+    }
+    
+    public void onClicked() {
+        int pairingStatus = getPairingStatus();
+        
+        if (isConnected()) {
+            askDisconnect();
+        } else if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED) {
+            connect();
+        } else if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_UNPAIRED) {
+            pair();
+        }
+    }
+    
+    public void disconnect() {
+        for (Profile profile : mProfiles) {
+            disconnect(profile);
+        }
+    }
+    
+    public void disconnect(Profile profile) {
+        LocalBluetoothProfileManager profileManager =
+                LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); 
+        int status = profileManager.getConnectionStatus(mAddress);
+        if (SettingsBtStatus.isConnectionStatusConnected(status)) {
+            profileManager.disconnect(mAddress);
+        }
+    }
+    
+    public void askDisconnect() {
+        Context context = mLocalManager.getForegroundActivity();
+        if (context == null) {
+            // Cannot ask, since we need an activity context
+            disconnect();
+            return;
+        }
+        
+        Resources res = context.getResources();
+        
+        String name = getName();
+        if (TextUtils.isEmpty(name)) {
+            name = res.getString(R.string.bluetooth_device);
+        }
+        String message = res.getString(R.string.bluetooth_disconnect_blank, name);
+        
+        DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
+                disconnect();
+            }
+        };
+        
+        AlertDialog ad = new AlertDialog.Builder(context)
+                .setTitle(getName())
+                .setMessage(message)
+                .setPositiveButton(android.R.string.ok, disconnectListener)
+                .setNegativeButton(android.R.string.cancel, null)
+                .show();
+    }
+    
+    public void connect() {
+        if (!ensurePaired()) return;
+        
+        Context context = mLocalManager.getContext();
+        boolean hasAtLeastOnePreferredProfile = false;
+        for (Profile profile : mProfiles) {
+            if (LocalBluetoothProfileManager.isPreferredProfile(context, mAddress, profile)) {
+                hasAtLeastOnePreferredProfile = true;
+                connect(profile);
+            }
+        }
+        
+        if (!hasAtLeastOnePreferredProfile) {
+            connectAndPreferAllProfiles();
+        }
+    }
+    
+    private void connectAndPreferAllProfiles() {
+        if (!ensurePaired()) return;
+        
+        Context context = mLocalManager.getContext();
+        for (Profile profile : mProfiles) {
+            LocalBluetoothProfileManager.setPreferredProfile(context, mAddress, profile, true);
+            connect(profile);
+        }
+    }
+    
+    public void connect(Profile profile) {
+        if (!ensurePaired()) return;
+        
+        LocalBluetoothProfileManager profileManager =
+                LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); 
+        int status = profileManager.getConnectionStatus(mAddress);
+        if (!SettingsBtStatus.isConnectionStatusConnected(status)) {
+            mIsConnectingErrorPossible = true;
+            if (profileManager.connect(mAddress) != BluetoothDevice.RESULT_SUCCESS) {
+                showConnectingError();
+            }
+        }
+    }
+    
+    public void showConnectingError() {
+        if (!mIsConnectingErrorPossible) return;
+        mIsConnectingErrorPossible = false;
+        
+        mLocalManager.showError(mAddress, R.string.bluetooth_error_title,
+                R.string.bluetooth_connecting_error_message);
+    }
+    
+    private boolean ensurePaired() {
+        if (getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_UNPAIRED) {
+            pair();
+            return false;
+        } else {
+            return true;
+        }
+    }
+    
+    public void pair() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+        
+        // Pairing doesn't work if scanning, so cancel
+        if (manager.isDiscovering()) {
+            manager.cancelDiscovery();
+        }
+        
+        if (mLocalManager.createBonding(mAddress)) {
+            setPairingStatus(SettingsBtStatus.PAIRING_STATUS_PAIRING);
+        }
+    }
+    
+    public void unpair() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+        
+        switch (getPairingStatus()) {
+            case SettingsBtStatus.PAIRING_STATUS_PAIRED:
+                manager.removeBonding(mAddress);
+                break;
+                
+            case SettingsBtStatus.PAIRING_STATUS_PAIRING:
+                manager.cancelBondingProcess(mAddress);
+                break;
+        }
+    }
+    
+    private void fillData() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+            
+        fetchName();        
+        mBtClass = manager.getRemoteClass(mAddress);
+
+        LocalBluetoothProfileManager.fill(mBtClass, mProfiles);
+            
+        mPairingStatus = manager.hasBonding(mAddress)
+                ? SettingsBtStatus.PAIRING_STATUS_PAIRED
+                : SettingsBtStatus.PAIRING_STATUS_UNPAIRED;
+            
+        mVisible = false;
+        
+        dispatchAttributesChanged();
+    }
+    
+    public String getAddress() {
+        return mAddress;
+    }
+
+    public String getName() {
+        return mName;
+    }
+    
+    public void refreshName() {
+        fetchName();
+        dispatchAttributesChanged();
+    }
+    
+    private void fetchName() {
+        mName = mLocalManager.getBluetoothManager().getRemoteName(mAddress);
+        
+        if (TextUtils.isEmpty(mName)) {
+            mName = mAddress;
+        }
+    }
+    
+    public void refresh() {
+        dispatchAttributesChanged();
+    }
+
+    public boolean isVisible() {
+        return mVisible;
+    }
+
+    void setVisible(boolean visible) {
+        if (mVisible != visible) {
+            mVisible = visible;
+            dispatchAttributesChanged();
+        }
+    }
+
+    public int getPairingStatus() {
+        return mPairingStatus;
+    }
+
+    void setPairingStatus(int pairingStatus) {
+        if (mPairingStatus != pairingStatus) {
+            mPairingStatus = pairingStatus;
+            dispatchAttributesChanged();
+        }
+    }
+    
+    void setRssi(short rssi) {
+        if (mRssi != rssi) {
+            mRssi = rssi;
+            dispatchAttributesChanged();
+        }
+    }
+    
+    /**
+     * Checks whether we are connected to this device (any profile counts).
+     * 
+     * @return Whether it is connected.
+     */
+    public boolean isConnected() {
+        for (Profile profile : mProfiles) {
+            int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
+                    .getConnectionStatus(mAddress);
+            if (SettingsBtStatus.isConnectionStatusConnected(status)) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    public boolean isBusy() {
+        for (Profile profile : mProfiles) {
+            int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile)
+                    .getConnectionStatus(mAddress); 
+            if (SettingsBtStatus.isConnectionStatusBusy(status)) {
+                return true;
+            }
+        }
+        
+        if (getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_PAIRING) {
+            return true;
+        }
+        
+        return false;
+    }
+    
+    public int getBtClassDrawable() {
+
+        // First try looking at profiles
+        if (mProfiles.contains(Profile.A2DP)) {
+            return R.drawable.ic_bt_headphones_a2dp;
+        } else if (mProfiles.contains(Profile.HEADSET)) {
+            return R.drawable.ic_bt_headset_hfp;
+        }
+        
+        // Fallback on class
+        switch (BluetoothClass.Device.Major.getDeviceMajor(mBtClass)) {
+        case BluetoothClass.Device.Major.COMPUTER:
+            return R.drawable.ic_bt_laptop;
+
+        case BluetoothClass.Device.Major.PHONE:
+            return R.drawable.ic_bt_cellphone;
+            
+        default:
+            return 0;
+        }
+    }
+
+    public int getSummary() {
+        // TODO: clean up
+        int oneOffSummary = getOneOffSummary();
+        if (oneOffSummary != 0) {
+            return oneOffSummary;
+        }
+        
+        for (Profile profile : mProfiles) {
+            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+                    .getProfileManager(mLocalManager, profile);
+            int connectionStatus = profileManager.getConnectionStatus(mAddress);
+            
+            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus) ||
+                    connectionStatus == SettingsBtStatus.CONNECTION_STATUS_CONNECTING ||
+                    connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING) {
+                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+            }
+        }
+        
+        int pairingStatus = getPairingStatus();
+        return SettingsBtStatus.getPairingStatusSummary(pairingStatus); 
+    }
+
+    /**
+     * We have special summaries when particular profiles are connected. This
+     * checks for those states and returns an applicable summary.
+     * 
+     * @return A one-off summary that is applicable for the current state, or 0. 
+     */
+    private int getOneOffSummary() {
+        boolean isA2dpConnected = false, isHeadsetConnected = false, isConnecting = false;
+        
+        if (mProfiles.contains(Profile.A2DP)) {
+            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+                    .getProfileManager(mLocalManager, Profile.A2DP);
+            isConnecting = profileManager.getConnectionStatus(mAddress) ==
+                    SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 
+            isA2dpConnected = profileManager.isConnected(mAddress);
+        }
+
+        if (mProfiles.contains(Profile.HEADSET)) {
+            LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+                    .getProfileManager(mLocalManager, Profile.HEADSET);
+            isConnecting |= profileManager.getConnectionStatus(mAddress) ==
+                    SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 
+            isHeadsetConnected = profileManager.isConnected(mAddress);
+        }
+        
+        if (isConnecting) {
+            // If any of these important profiles is connecting, prefer that
+            return SettingsBtStatus.getConnectionStatusSummary(
+                    SettingsBtStatus.CONNECTION_STATUS_CONNECTING);
+        } else if (isA2dpConnected && isHeadsetConnected) {
+            return R.string.bluetooth_summary_connected_to_a2dp_headset;
+        } else if (isA2dpConnected) {
+            return R.string.bluetooth_summary_connected_to_a2dp;
+        } else if (isHeadsetConnected) {
+            return R.string.bluetooth_summary_connected_to_headset;
+        } else {
+            return 0;
+        }
+    }
+    
+    public List<Profile> getProfiles() {
+        return new ArrayList<Profile>(mProfiles);
+    }
+
+    public void onCreateContextMenu(ContextMenu menu) {
+        // No context menu if it is busy (none of these items are applicable if busy)
+        if (isBusy()) return;
+        
+        // No context menu if there are no profiles
+        if (mProfiles.size() == 0) return;
+        
+        int pairingStatus = getPairingStatus();
+        boolean isConnected = isConnected();
+        
+        menu.setHeaderTitle(getName());
+        
+        if (isConnected) {
+            menu.add(0, CONTEXT_ITEM_DISCONNECT, 0, R.string.bluetooth_device_context_disconnect);
+        } else {
+            // For connection action, show either "Connect" or "Pair & connect"
+            int connectString = pairingStatus == SettingsBtStatus.PAIRING_STATUS_UNPAIRED
+                    ? R.string.bluetooth_device_context_pair_connect
+                    : R.string.bluetooth_device_context_connect;
+            menu.add(0, CONTEXT_ITEM_CONNECT, 0, connectString);
+        }
+        
+        if (pairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED) {
+            // For unpair action, show either "Unpair" or "Disconnect & unpair"
+            int unpairString = isConnected
+                    ? R.string.bluetooth_device_context_disconnect_unpair
+                    : R.string.bluetooth_device_context_unpair;
+            menu.add(0, CONTEXT_ITEM_UNPAIR, 0, unpairString);
+
+            // Show the connection options item
+            menu.add(0, CONTEXT_ITEM_CONNECT_ADVANCED, 0,
+                    R.string.bluetooth_device_context_connect_advanced);
+        }
+    }
+
+    /**
+     * Called when a context menu item is clicked.
+     * 
+     * @param item The item that was clicked.
+     */
+    public void onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case CONTEXT_ITEM_DISCONNECT:
+                disconnect();
+                break;
+                
+            case CONTEXT_ITEM_CONNECT:
+                connect();
+                break;
+                
+            case CONTEXT_ITEM_UNPAIR:
+                unpair();
+                break;
+                
+            case CONTEXT_ITEM_CONNECT_ADVANCED:
+                Intent intent = new Intent();
+                // Need an activity context to open this in our task
+                Context context = mLocalManager.getForegroundActivity();
+                if (context == null) {
+                    // Fallback on application context, and open in a new task
+                    context = mLocalManager.getContext();
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                }
+                intent.setClass(context, ConnectSpecificProfilesActivity.class);
+                intent.putExtra(ConnectSpecificProfilesActivity.EXTRA_ADDRESS, mAddress);
+                context.startActivity(intent);
+                break;
+        }
+    }
+
+    public void registerCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+        }
+    }
+    
+    public void unregisterCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.remove(callback);
+        }
+    }
+    
+    private void dispatchAttributesChanged() {
+        synchronized (mCallbacks) {
+            for (Callback callback : mCallbacks) {
+                callback.onDeviceAttributesChanged(this);
+            }
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return mAddress;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if ((o == null) || !(o instanceof LocalBluetoothDevice)) {
+            throw new ClassCastException();
+        }
+        
+        return mAddress.equals(((LocalBluetoothDevice) o).mAddress);
+    }
+
+    @Override
+    public int hashCode() {
+        return mAddress.hashCode();
+    }
+    
+    public int compareTo(LocalBluetoothDevice another) {
+        int comparison;        
+        
+        // Connected above not connected
+        comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
+        if (comparison != 0) return comparison;
+        
+        // Paired above not paired
+        comparison = (another.mPairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED ? 1 : 0) -
+            (mPairingStatus == SettingsBtStatus.PAIRING_STATUS_PAIRED ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        // Visible above not visible
+        comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
+        if (comparison != 0) return comparison;
+        
+        // Stronger signal above weaker signal
+        comparison = another.mRssi - mRssi;
+        if (comparison != 0) return comparison;
+        
+        // Fallback on name
+        return getName().compareTo(another.getName());
+    }
+
+    public interface Callback {
+        void onDeviceAttributesChanged(LocalBluetoothDevice device);
+    }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java
new file mode 100644
index 0000000..48a41f1
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.util.Log;
+import android.widget.Toast;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.LocalBluetoothManager.Callback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LocalBluetoothDeviceManager manages the set of remote Bluetooth devices.
+ */
+public class LocalBluetoothDeviceManager {
+    private static final String TAG = "LocalBluetoothDeviceManager";
+
+    final LocalBluetoothManager mLocalManager;
+    final List<Callback> mCallbacks;
+    
+    final List<LocalBluetoothDevice> mDevices = new ArrayList<LocalBluetoothDevice>();
+
+    public LocalBluetoothDeviceManager(LocalBluetoothManager localManager) {
+        mLocalManager = localManager;
+        mCallbacks = localManager.getCallbacks();
+        readPairedDevices();
+    }
+
+    private synchronized void readPairedDevices() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+        String[] bondedAddresses = manager.listBondings();
+        if (bondedAddresses == null) return;
+        
+        for (String address : bondedAddresses) {
+            LocalBluetoothDevice device = findDevice(address);
+            if (device == null) {
+                device = new LocalBluetoothDevice(mLocalManager.getContext(), address);
+                mDevices.add(device);
+                dispatchDeviceAdded(device);                
+            }
+        }
+    }
+    
+    public synchronized List<LocalBluetoothDevice> getDevicesCopy() {
+        return new ArrayList<LocalBluetoothDevice>(mDevices);
+    }
+    
+    void onBluetoothStateChanged(boolean enabled) {
+        if (enabled) {
+            readPairedDevices();
+        }
+    }
+
+    public synchronized void onDeviceAppeared(String address, short rssi) {
+        boolean deviceAdded = false;
+        
+        LocalBluetoothDevice device = findDevice(address);
+        if (device == null) {
+            device = new LocalBluetoothDevice(mLocalManager.getContext(), address);
+            mDevices.add(device);
+            deviceAdded = true;
+        }
+        
+        device.setRssi(rssi);
+        device.setVisible(true);
+        
+        if (deviceAdded) {
+            dispatchDeviceAdded(device);
+        }
+    }
+    
+    public synchronized void onDeviceDisappeared(String address) {
+        LocalBluetoothDevice device = findDevice(address);
+        if (device == null) return;
+        
+        device.setVisible(false);
+        checkForDeviceRemoval(device);
+    }
+    
+    private void checkForDeviceRemoval(LocalBluetoothDevice device) {
+        if (device.getPairingStatus() == SettingsBtStatus.PAIRING_STATUS_UNPAIRED &&
+                !device.isVisible()) {
+            // If device isn't paired, remove it altogether
+            mDevices.remove(device);
+            dispatchDeviceDeleted(device);
+        }            
+    }
+    
+    public synchronized void onDeviceNameUpdated(String address) {
+        LocalBluetoothDevice device = findDevice(address);
+        if (device != null) {
+            device.refreshName();
+        }
+    }
+
+    public synchronized LocalBluetoothDevice findDevice(String address) {
+        
+        for (int i = mDevices.size() - 1; i >= 0; i--) {
+            LocalBluetoothDevice device = mDevices.get(i);
+            
+            if (device.getAddress().equals(address)) {
+                return device;
+            }
+        }
+        
+        return null;
+    }
+    
+    /**
+     * Attempts to get the name of a remote device, otherwise returns the address.
+     * 
+     * @param address The address.
+     * @return The name, or if unavailable, the address.
+     */
+    public String getName(String address) {
+        LocalBluetoothDevice device = findDevice(address);
+        return device != null ? device.getName() : address;
+    }
+    
+    private void dispatchDeviceAdded(LocalBluetoothDevice device) {
+        synchronized (mCallbacks) {
+            for (Callback callback : mCallbacks) {
+                callback.onDeviceAdded(device);
+            }
+        }
+        
+        // TODO: divider between prev paired/connected and scanned
+    }
+    
+    private void dispatchDeviceDeleted(LocalBluetoothDevice device) {
+        synchronized (mCallbacks) {
+            for (Callback callback : mCallbacks) {
+                callback.onDeviceDeleted(device);
+            }
+        }
+    }
+
+    public synchronized void onBondingStateChanged(String address, boolean created) {
+        LocalBluetoothDevice device = findDevice(address);
+        if (device == null) {
+            Log.e(TAG, "Got bonding state changed for " + address +
+                    ", but we have no record of that device.");
+            return;
+        }
+        
+        device.setPairingStatus(created ? SettingsBtStatus.PAIRING_STATUS_PAIRED
+                : SettingsBtStatus.PAIRING_STATUS_UNPAIRED);
+        checkForDeviceRemoval(device);
+
+        if (created) {
+            // Auto-connect after pairing
+            device.connect();
+        }
+    }
+    
+    public synchronized void onBondingError(String address) {
+        mLocalManager.showError(address, R.string.bluetooth_error_title,
+                R.string.bluetooth_pairing_error_message);
+    }
+    
+    public synchronized void onProfileStateChanged(String address) {
+        LocalBluetoothDevice device = findDevice(address);
+        if (device == null) return;
+        
+        device.refresh();
+    }
+    
+    public synchronized void onConnectingError(String address) {
+        LocalBluetoothDevice device = findDevice(address);
+        if (device == null) return;
+        
+        /*
+         * Go through the device's delegate so we don't spam the user with
+         * errors connecting to different profiles, and instead make sure the
+         * user sees a single error for his single 'connect' action.
+         */
+        device.showConnectingError();
+    }
+    
+    public synchronized void onScanningStateChanged(boolean started) {
+        if (!started) return;
+        
+        // If starting a new scan, clear old visibility
+        for (int i = mDevices.size() - 1; i >= 0; i--) {
+            LocalBluetoothDevice device = mDevices.get(i);
+            device.setVisible(false);
+            checkForDeviceRemoval(device);
+        }
+    }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
new file mode 100644
index 0000000..9db9e77
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+import android.widget.Toast;
+
+// TODO: have some notion of shutting down.  Maybe a minute after they leave BT settings?
+/**
+ * LocalBluetoothManager provides a simplified interface on top of a subset of
+ * the Bluetooth API.
+ */
+public class LocalBluetoothManager {
+    private static final String TAG = "LocalBluetoothManager";
+    static final boolean V = true;
+    
+    public static final String EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION =
+        "com.android.settings.bluetooth.intent.action.EXTENDED_BLUETOOTH_STATE_CHANGED";
+    private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings";
+    
+    private static LocalBluetoothManager INSTANCE;
+    /** Used when obtaining a reference to the singleton instance. */
+    private static Object INSTANCE_LOCK = new Object();
+    private boolean mInitialized;
+    
+    private Context mContext;
+    /** If a BT-related activity is in the foreground, this will be it. */
+    private Activity mForegroundActivity;
+    
+    private BluetoothDevice mManager;
+
+    private LocalBluetoothDeviceManager mLocalDeviceManager;
+    private BluetoothEventRedirector mEventRedirector;
+    
+    public static enum ExtendedBluetoothState { ENABLED, ENABLING, DISABLED, DISABLING, UNKNOWN }
+    private ExtendedBluetoothState mState = ExtendedBluetoothState.UNKNOWN;
+
+    private List<Callback> mCallbacks = new ArrayList<Callback>();
+    
+    private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
+    private long mLastScan;
+    
+    public static LocalBluetoothManager getInstance(Context context) {
+        synchronized (INSTANCE_LOCK) {
+            if (INSTANCE == null) {
+                INSTANCE = new LocalBluetoothManager();
+            }
+            
+            if (!INSTANCE.init(context)) {
+                return null;
+            }
+            
+            return INSTANCE;
+        }
+    }
+
+    private boolean init(Context context) {
+        if (mInitialized) return true;
+        mInitialized = true;
+        
+        // This will be around as long as this process is
+        mContext = context.getApplicationContext();
+        
+        mManager = (BluetoothDevice) context.getSystemService(Context.BLUETOOTH_SERVICE);
+        if (mManager == null) {
+            return false;
+        }
+        
+        mLocalDeviceManager = new LocalBluetoothDeviceManager(this);
+
+        mEventRedirector = new BluetoothEventRedirector(this);
+        mEventRedirector.start();
+       
+        return true;
+    }
+    
+    public BluetoothDevice getBluetoothManager() {
+        return mManager;
+    }
+    
+    public Context getContext() {
+        return mContext;
+    }
+
+    public Activity getForegroundActivity() {
+        return mForegroundActivity;
+    }
+    
+    public void setForegroundActivity(Activity activity) {
+        mForegroundActivity = activity;
+    }
+    
+    public SharedPreferences getSharedPreferences() {
+        return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+    }
+    
+    public LocalBluetoothDeviceManager getLocalDeviceManager() {
+        return mLocalDeviceManager;
+    }
+    
+    List<Callback> getCallbacks() {
+        return mCallbacks;
+    }
+    
+    public void registerCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+        }
+    }
+    
+    public void unregisterCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.remove(callback);
+        }
+    }
+    
+    public void startScanning(boolean force) {
+        if (mManager.isDiscovering()) {
+            /*
+             * Already discovering, but give the callback that information.
+             * Note: we only call the callbacks, not the same path as if the
+             * scanning state had really changed (in that case the device
+             * manager would clear its list of unpaired scanned devices).
+             */ 
+            dispatchScanningStateChanged(true);
+        } else {
+            
+            // Don't scan more than frequently than SCAN_EXPIRATION_MS, unless forced
+            if (!force && mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) return;
+            
+            if (mManager.startDiscovery(true)) {
+                mLastScan = System.currentTimeMillis();
+            }
+        }
+    }
+    
+    public ExtendedBluetoothState getBluetoothState() {
+        
+        if (mState == ExtendedBluetoothState.UNKNOWN) {
+            syncBluetoothState();
+        }
+            
+        return mState;
+    }
+    
+    void setBluetoothStateInt(ExtendedBluetoothState state) {
+        mState = state;
+        
+        /*
+         * TODO: change to callback method. originally it was broadcast to
+         * parallel the framework's method, but it just complicates things here.
+         */
+        // If this were a real API, I'd add as an extra
+        mContext.sendBroadcast(new Intent(EXTENDED_BLUETOOTH_STATE_CHANGED_ACTION));
+        
+        if (state == ExtendedBluetoothState.ENABLED || state == ExtendedBluetoothState.DISABLED) {
+            mLocalDeviceManager.onBluetoothStateChanged(state == ExtendedBluetoothState.ENABLED);
+        }
+    }
+    
+    private void syncBluetoothState() {
+        setBluetoothStateInt(mManager.isEnabled()
+                ? ExtendedBluetoothState.ENABLED
+                : ExtendedBluetoothState.DISABLED);
+    }
+
+    public void setBluetoothEnabled(boolean enabled) {
+        boolean wasSetStateSuccessful = enabled
+                ? mManager.enable()
+                : mManager.disable();
+                
+        if (wasSetStateSuccessful) {
+            setBluetoothStateInt(enabled
+                    ? ExtendedBluetoothState.ENABLING
+                    : ExtendedBluetoothState.DISABLING);
+        } else {
+            if (V) {
+                Log.v(TAG,
+                        "setBluetoothEnabled call, manager didn't return success for enabled: "
+                                + enabled);
+            }
+            
+            syncBluetoothState();
+        }
+    }
+    
+    /**
+     * @param started True if scanning started, false if scanning finished.
+     */
+    void onScanningStateChanged(boolean started) {
+        // TODO: have it be a callback (once we switch bluetooth state changed to callback)
+        mLocalDeviceManager.onScanningStateChanged(started);
+        dispatchScanningStateChanged(started);
+    }
+    
+    private void dispatchScanningStateChanged(boolean started) {
+        synchronized (mCallbacks) {
+            for (Callback callback : mCallbacks) {
+                callback.onScanningStateChanged(started);
+            }
+        }
+    }
+
+    public boolean createBonding(String address) {
+        return mManager.createBonding(address, mEventRedirector.getBluetoothDeviceCallback());
+    }
+    
+    public void showError(String address, int titleResId, int messageResId) {
+        LocalBluetoothDevice device = mLocalDeviceManager.findDevice(address);
+        if (device == null) return;
+
+        String name = device.getName();
+        String message = mContext.getString(messageResId, name);
+
+        if (mForegroundActivity != null) {
+            // Need an activity context to show a dialog
+            AlertDialog ad = new AlertDialog.Builder(mForegroundActivity)
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setTitle(titleResId)
+                .setMessage(message)
+                .setPositiveButton(android.R.string.ok, null)
+                .show();
+        } else {
+            // Fallback on a toast 
+            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    public interface Callback {
+        void onScanningStateChanged(boolean started);
+        void onDeviceAdded(LocalBluetoothDevice device);
+        void onDeviceDeleted(LocalBluetoothDevice device);
+    }
+    
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
new file mode 100644
index 0000000..b614712
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothClass;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LocalBluetoothProfileManager is an abstract class defining the basic
+ * functionality related to a profile.
+ */
+public abstract class LocalBluetoothProfileManager {
+
+    // TODO: close profiles when we're shutting down
+    private static Map<Profile, LocalBluetoothProfileManager> sProfileMap =
+            new HashMap<Profile, LocalBluetoothProfileManager>(); 
+    
+    protected LocalBluetoothManager mLocalManager;
+    
+    public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager,
+            Profile profile) {
+        
+        LocalBluetoothProfileManager profileManager;
+        
+        synchronized (sProfileMap) {
+            profileManager = sProfileMap.get(profile);
+            
+            if (profileManager == null) {
+                switch (profile) {
+                case A2DP:
+                    profileManager = new A2dpProfileManager(localManager);
+                    break;
+                    
+                case HEADSET:
+                    profileManager = new HeadsetProfileManager(localManager);
+                    break;
+                }
+                
+                sProfileMap.put(profile, profileManager);    
+            }
+        }
+        
+        return profileManager;
+    }
+
+    // TODO: remove once the framework has this API
+    public static boolean isPreferredProfile(Context context, String address, Profile profile) {
+        return getPreferredProfileSharedPreferences(context).getBoolean(
+                getPreferredProfileKey(address, profile), true);
+    }
+    
+    public static void setPreferredProfile(Context context, String address, Profile profile,
+            boolean preferred) {
+        getPreferredProfileSharedPreferences(context).edit().putBoolean(
+                getPreferredProfileKey(address, profile), preferred).commit();
+    }
+
+    private static SharedPreferences getPreferredProfileSharedPreferences(Context context) {
+        return context.getSharedPreferences("bluetooth_preferred_profiles", Context.MODE_PRIVATE);
+    }
+    
+    private static String getPreferredProfileKey(String address, Profile profile) {
+        return address + "_" + profile.toString();
+    }
+    
+    /**
+     * Temporary method to fill profiles based on a device's class.
+     * 
+     * @param btClass The class
+     * @param profiles The list of profiles to fill
+     */
+    public static void fill(int btClass, List<Profile> profiles) {
+        profiles.clear();
+
+        if (A2dpProfileManager.doesClassMatch(btClass)) {
+            profiles.add(Profile.A2DP);
+        }
+        
+        if (HeadsetProfileManager.doesClassMatch(btClass)) {
+            profiles.add(Profile.HEADSET);
+        }
+    }
+
+    protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) {
+        mLocalManager = localManager;
+    }
+    
+    public abstract int connect(String address);
+    
+    public abstract int disconnect(String address);
+    
+    public abstract int getConnectionStatus(String address);
+
+    public abstract int getSummary(String address);
+
+    public boolean isConnected(String address) {
+        return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(address));
+    }
+    
+    // TODO: int instead of enum
+    public enum Profile {
+        HEADSET(R.string.bluetooth_profile_headset),
+        A2DP(R.string.bluetooth_profile_a2dp);
+        
+        public final int localizedString;
+        
+        private Profile(int localizedString) {
+            this.localizedString = localizedString;
+        }
+    }
+
+    /**
+     * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service. 
+     */
+    private static class A2dpProfileManager extends LocalBluetoothProfileManager {
+        private BluetoothA2dp mService;
+        
+        public A2dpProfileManager(LocalBluetoothManager localManager) {
+            super(localManager);
+            
+            mService = new BluetoothA2dp(localManager.getContext());
+            // TODO: block until connection?
+        }
+
+        @Override
+        public int connect(String address) {
+            return mService.connectSink(address);
+        }
+
+        @Override
+        public int disconnect(String address) {
+            return mService.disconnectSink(address);
+        }
+        
+        static boolean doesClassMatch(int btClass) {
+            if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) {
+                return true;
+            }
+
+            // By the specification A2DP sinks must indicate the RENDER service
+            // class, but some do not (Chordette). So match on a few more to be
+            // safe
+            switch (BluetoothClass.Device.getDevice(btClass)) {
+            case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
+            case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
+            case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER:
+            case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+                return true;
+                
+            default:
+                return false;
+            }
+        }
+
+        @Override
+        public int getConnectionStatus(String address) {
+            return convertState(mService.getSinkState(address));
+        }
+        
+        @Override
+        public int getSummary(String address) {
+            int connectionStatus = getConnectionStatus(address);
+            
+            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
+                return R.string.bluetooth_a2dp_profile_summary_connected;
+            } else {
+                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+            }
+        }
+
+        private static int convertState(int a2dpState) {
+            switch (a2dpState) {
+            case BluetoothA2dp.STATE_CONNECTED:
+                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
+            case BluetoothA2dp.STATE_CONNECTING:
+                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+            case BluetoothA2dp.STATE_DISCONNECTED:
+                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+            case BluetoothA2dp.STATE_DISCONNECTING:
+                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
+            case BluetoothA2dp.STATE_PLAYING:
+                return SettingsBtStatus.CONNECTION_STATUS_ACTIVE;
+            default:
+                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
+            }
+        }
+    }
+    
+    /**
+     * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service. 
+     */
+    private static class HeadsetProfileManager extends LocalBluetoothProfileManager {
+        private BluetoothHeadset mService;
+        
+        public HeadsetProfileManager(LocalBluetoothManager localManager) {
+            super(localManager);
+            
+//            final boolean[] isServiceConnected = new boolean[1];
+//            BluetoothHeadset.ServiceListener l = new BluetoothHeadset.ServiceListener() {
+//                public void onServiceConnected() {
+//                    synchronized (this) {
+//                        isServiceConnected[0] = true;
+//                        notifyAll();
+//                    }
+//                }
+//                public void onServiceDisconnected() {
+//                    mService = null;
+//                }
+//            };
+            
+            // TODO: block, but can't on UI thread
+            mService = new BluetoothHeadset(localManager.getContext(), null);
+
+//            synchronized (l) {
+//                while (!isServiceConnected[0]) {
+//                    try {
+//                        l.wait(100);
+//                    } catch (InterruptedException e) {
+//                        throw new IllegalStateException(e);
+//                    }
+//                }
+//            }
+        }
+
+        @Override
+        public int connect(String address) {
+            // Since connectHeadset fails if already connected to a headset, we
+            // disconnect from any headset first
+            mService.disconnectHeadset();
+            return mService.connectHeadset(address, null)
+                    ? BluetoothError.SUCCESS : BluetoothError.ERROR;
+        }
+
+        @Override
+        public int disconnect(String address) {
+            if (mService.getHeadsetAddress().equals(address)) {
+                return mService.disconnectHeadset() ? BluetoothError.SUCCESS : BluetoothError.ERROR;
+            } else {
+                return BluetoothError.SUCCESS;
+            }
+        }
+        
+        static boolean doesClassMatch(int btClass) {
+            switch (BluetoothClass.Device.getDevice(btClass)) {
+            case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
+            case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+            case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
+                return true;
+                
+            default:
+                return false;
+            }
+        }
+
+        @Override
+        public int getConnectionStatus(String address) {
+            String headsetAddress = mService.getHeadsetAddress();
+            return headsetAddress != null && headsetAddress.equals(address)
+                    ? convertState(mService.getState())
+                    : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+        }
+        
+        @Override
+        public int getSummary(String address) {
+            int connectionStatus = getConnectionStatus(address);
+            
+            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
+                return R.string.bluetooth_headset_profile_summary_connected;
+            } else {
+                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
+            }
+        }
+
+        private static int convertState(int headsetState) {
+            switch (headsetState) {
+            case BluetoothHeadset.STATE_CONNECTED:
+                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
+            case BluetoothHeadset.STATE_CONNECTING:
+                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
+            case BluetoothHeadset.STATE_DISCONNECTED:
+                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
+            default:
+                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
+            }
+        }
+    }
+    
+}
diff --git a/src/com/android/settings/bluetooth/SettingsBtStatus.java b/src/com/android/settings/bluetooth/SettingsBtStatus.java
new file mode 100644
index 0000000..051d666
--- /dev/null
+++ b/src/com/android/settings/bluetooth/SettingsBtStatus.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008 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.settings.bluetooth;
+
+import com.android.settings.R;
+
+/**
+ * SettingsBtStatus is a helper class that contains constants for various status
+ * codes.
+ */
+public class SettingsBtStatus {
+    private static final String TAG = "SettingsBtStatus";
+   
+    // Connection status
+    
+    public static final int CONNECTION_STATUS_UNKNOWN = 0;
+    public static final int CONNECTION_STATUS_ACTIVE = 1;
+    /** Use {@link #isConnected} to check for the connected state */
+    public static final int CONNECTION_STATUS_CONNECTED = 2;
+    public static final int CONNECTION_STATUS_CONNECTING = 3;
+    public static final int CONNECTION_STATUS_DISCONNECTED = 4;
+    public static final int CONNECTION_STATUS_DISCONNECTING = 5;
+
+    public static final int getConnectionStatusSummary(int connectionStatus) {
+        switch (connectionStatus) {
+        case CONNECTION_STATUS_ACTIVE:
+            return R.string.bluetooth_connected;
+        case CONNECTION_STATUS_CONNECTED:
+            return R.string.bluetooth_connected;
+        case CONNECTION_STATUS_CONNECTING:
+            return R.string.bluetooth_connecting;
+        case CONNECTION_STATUS_DISCONNECTED:
+            return R.string.bluetooth_disconnected;
+        case CONNECTION_STATUS_DISCONNECTING:
+            return R.string.bluetooth_disconnecting;
+        case CONNECTION_STATUS_UNKNOWN:
+            return R.string.bluetooth_unknown;
+        default:
+            return 0;
+        }
+    }
+    
+    public static final boolean isConnectionStatusConnected(int connectionStatus) {
+        return connectionStatus == CONNECTION_STATUS_ACTIVE
+                || connectionStatus == CONNECTION_STATUS_CONNECTED;
+    }
+    
+    public static final boolean isConnectionStatusBusy(int connectionStatus) {
+        return connectionStatus == CONNECTION_STATUS_CONNECTING
+                || connectionStatus == CONNECTION_STATUS_DISCONNECTING;
+    }
+    
+    // Pairing status
+    
+    public static final int PAIRING_STATUS_UNPAIRED = 0;
+    public static final int PAIRING_STATUS_PAIRED = 1;
+    public static final int PAIRING_STATUS_PAIRING = 2;
+
+    public static final int getPairingStatusSummary(int pairingStatus) {
+        switch (pairingStatus) {
+        case PAIRING_STATUS_PAIRED:
+            return R.string.bluetooth_paired;
+        case PAIRING_STATUS_PAIRING:
+            return R.string.bluetooth_pairing;
+        case PAIRING_STATUS_UNPAIRED:
+            return R.string.bluetooth_not_connected;
+        default:
+            return 0;
+        }
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/Memory.java b/src/com/android/settings/deviceinfo/Memory.java
index 8a3cb8b..86e6423 100644
--- a/src/com/android/settings/deviceinfo/Memory.java
+++ b/src/com/android/settings/deviceinfo/Memory.java
@@ -22,6 +22,7 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.Environment;
 import android.os.IMountService;
@@ -30,6 +31,7 @@
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceScreen;
+import android.util.Log;
 
 import com.android.settings.R;
 
@@ -37,6 +39,8 @@
 import java.text.DecimalFormat;
 
 public class Memory extends PreferenceActivity {
+    
+    private static final String TAG = "Memory";
 
     private static final String MEMORY_SD_SIZE = "memory_sd_size";
 
@@ -50,15 +54,14 @@
     private Preference mSdAvail;
     private Preference mSdUnmount;
     
-    private IMountService   mMountService;
+    // Access using getMountService()
+    private IMountService mMountService = null;
 
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         
         addPreferencesFromResource(R.xml.device_info_memory);
-
-        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
         
         mRes = getResources();
         mSdSize = findPreference(MEMORY_SD_SIZE);
@@ -89,6 +92,18 @@
         unregisterReceiver(mReceiver);
     }
     
+    private synchronized IMountService getMountService() {
+       if (mMountService == null) {
+           IBinder service = ServiceManager.getService("mount");
+           if (service != null) {
+               mMountService = IMountService.Stub.asInterface(service);
+           } else {
+               Log.e(TAG, "Can't get mount service");
+           }
+       }
+       return mMountService;
+    }
+    
     @Override
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
         if (preference == mSdUnmount) {
@@ -107,8 +122,13 @@
     };
 
     private void unmount() {
+        IMountService mountService = getMountService();
         try {
-            mMountService.unmountMedia(Environment.getExternalStorageDirectory().toString());
+            if (mountService != null) {
+                mountService.unmountMedia(Environment.getExternalStorageDirectory().toString());
+            } else {
+                Log.e(TAG, "Mount service is null, can't unmount");
+            }
         } catch (RemoteException ex) {
             // Failed for some reason, try to update UI to actual state
             updateMemoryStatus();
diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java
index a56e607..b7aceb1 100644
--- a/src/com/android/settings/deviceinfo/Status.java
+++ b/src/com/android/settings/deviceinfo/Status.java
@@ -240,9 +240,11 @@
     }
 
     private void setSummaryText(String preference, String text) {
-        if (text != null) {
-            findPreference(preference).setSummary(text);
+        if (TextUtils.isEmpty(text)) {
+            text = sUnknown;
         }
+        
+        findPreference(preference).setSummary(text);
     }
     
     private void updateNetworkType() {
diff --git a/src/com/android/settings/quicklaunch/QuickLaunchSettings.java b/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
index 1b9dff4..df15c0b 100644
--- a/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
+++ b/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
@@ -263,7 +263,7 @@
         KeyCharacterMap keyMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD);
 
         // Go through all the key codes and create a preference for the appropriate keys
-        for (int keyCode = KeyEvent.MAX_KEYCODE - 1; keyCode >= 0; keyCode--) {
+        for (int keyCode = KeyEvent.getMaxKeyCode() - 1; keyCode >= 0; keyCode--) {
             // Get the label for the primary char on the key that produces this key code
             char shortcut = (char) Character.toLowerCase(keyMap.getDisplayLabel(keyCode));
             if (shortcut == 0 || shortcutSeen.get(shortcut, false)) continue;
diff --git a/src/com/android/settings/wifi/AccessPointDialog.java b/src/com/android/settings/wifi/AccessPointDialog.java
index 95e469f..917ed96 100644
--- a/src/com/android/settings/wifi/AccessPointDialog.java
+++ b/src/com/android/settings/wifi/AccessPointDialog.java
@@ -25,6 +25,7 @@
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.text.TextUtils;
+import android.text.format.Formatter;
 import android.text.method.PasswordTransformationMethod;
 import android.text.method.TransformationMethod;
 import android.util.Log;
@@ -303,7 +304,7 @@
             }
     
             if (mState.primary && mState.ipAddress != 0) {
-                addInfoRow(R.string.ip_address, ipAddressToString(mState.ipAddress));
+                addInfoRow(R.string.ip_address, Formatter.formatIpAddress(mState.ipAddress));
             }
             
         } else if (mMode == MODE_CONFIGURE) {
@@ -579,14 +580,6 @@
         return 0;
     }
     
-    private static String ipAddressToString(int addr) {
-        StringBuffer buf = new StringBuffer();
-        buf.append(addr  & 0xff).append('.').
-            append((addr >>>= 8) & 0xff).append('.').
-            append((addr >>>= 8) & 0xff).append('.').
-            append((addr >>>= 8) & 0xff);
-        return buf.toString();
-    }
 
     public void onClick(View v) {
         if (v == mShowPasswordCheckBox) {
diff --git a/src/com/android/settings/wifi/AccessPointState.java b/src/com/android/settings/wifi/AccessPointState.java
index 8dabbd1..c224954 100644
--- a/src/com/android/settings/wifi/AccessPointState.java
+++ b/src/com/android/settings/wifi/AccessPointState.java
@@ -538,7 +538,13 @@
             
             // If password is empty, it should be left untouched
             if (!TextUtils.isEmpty(mPassword)) {
-                config.preSharedKey = convertToQuotedString(mPassword);
+                if (mPassword.length() == 64 && isHex(mPassword)) {
+                    // Goes unquoted as hex
+                    config.preSharedKey = mPassword;
+                } else {
+                    // Goes quoted as ASCII
+                    config.preSharedKey = convertToQuotedString(mPassword);
+                }
             }
             
         } else if (security.equals(OPEN)) {
@@ -554,8 +560,12 @@
             return false;
         }
         
-        for (int i = len - 1; i >= 0; i--) {
-            final char c = wepKey.charAt(i);
+        return isHex(wepKey);
+    }
+    
+    private static boolean isHex(String key) {
+        for (int i = key.length() - 1; i >= 0; i--) {
+            final char c = key.charAt(i);
             if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f')) {
                 return false;
             }
diff --git a/src/com/android/settings/wifi/IpSettings.java b/src/com/android/settings/wifi/IpSettings.java
index 592e8da..5a494fa 100644
--- a/src/com/android/settings/wifi/IpSettings.java
+++ b/src/com/android/settings/wifi/IpSettings.java
@@ -19,12 +19,16 @@
 import com.android.settings.R;
 
 import android.content.ContentResolver;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
 import android.preference.EditTextPreference;
+import android.preference.ListPreference;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
 import android.provider.Settings.System;
+import android.text.TextUtils;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -32,8 +36,10 @@
 
 public class IpSettings extends PreferenceActivity implements Preference.OnPreferenceChangeListener {
 
+    private static final String KEY_MAC_ADDRESS = "mac_address";
     private static final String KEY_USE_STATIC_IP = "use_static_ip";
-
+    private static final String KEY_NUM_CHANNELS = "num_channels";
+    
     private String[] mSettingNames = {
             System.WIFI_STATIC_IP, System.WIFI_STATIC_GATEWAY, System.WIFI_STATIC_NETMASK,
             System.WIFI_STATIC_DNS1, System.WIFI_STATIC_DNS2
@@ -61,12 +67,46 @@
             preference.setOnPreferenceChangeListener(this);
         }
     }
-
+    
     @Override
     protected void onResume() {
         super.onResume();
         
         updateUi();
+        initNumChannelsPreference();
+        refreshMacAddress();
+    }
+
+    private void initNumChannelsPreference() {
+        ListPreference pref = (ListPreference) findPreference(KEY_NUM_CHANNELS);
+        pref.setOnPreferenceChangeListener(this);
+
+        WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+        /*
+         * Generate the list of valid channel counts to show in the ListPreference.
+         * The values are numerical, so the only text to be localized is the
+         * "channel_word" resource.
+         */
+        int[] validChannelCounts = wifiManager.getValidChannelCounts();
+        if (validChannelCounts == null) {
+            Toast.makeText(this, R.string.wifi_setting_num_channels_error,
+                           Toast.LENGTH_SHORT).show();
+            return;
+        }
+        String[] entries = new String[validChannelCounts.length];
+        String[] entryValues = new String[validChannelCounts.length];
+
+        for (int i = 0; i < validChannelCounts.length; i++) {
+            entryValues[i] = String.valueOf(validChannelCounts[i]);
+            entries[i] = getString(R.string.wifi_setting_num_channels_channel_phrase,
+                                   validChannelCounts[i]);
+        }
+        pref.setEntries(entries);
+        pref.setEntryValues(entryValues);
+        int numChannels = wifiManager.getNumAllowedChannels();
+        if (numChannels >= 0) {
+            pref.setValue(String.valueOf(numChannels));
+        }
     }
 
     @Override
@@ -80,14 +120,34 @@
     }
 
     public boolean onPreferenceChange(Preference preference, Object newValue) {
-        String value = (String) newValue;
-        
-        if (!isIpAddress(value)) {
-            Toast.makeText(this, R.string.wifi_ip_settings_invalid_ip, Toast.LENGTH_LONG).show();
-            return false;
+        String key = preference.getKey();
+        if (key == null) return true;
+
+        if (key.equals(KEY_NUM_CHANNELS)) {
+            try {
+                int numChannels = Integer.parseInt((String) newValue);
+                WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+                if (!wifiManager.setNumAllowedChannels(numChannels)) {
+                    Toast.makeText(this, R.string.wifi_setting_num_channels_error,
+                            Toast.LENGTH_SHORT).show();
+                }
+            } catch (NumberFormatException e) {
+                Toast.makeText(this, R.string.wifi_setting_num_channels_error,
+                        Toast.LENGTH_SHORT).show();
+                return false;
+            }
+            
+        } else {
+            String value = (String) newValue;
+            
+            if (!isIpAddress(value)) {
+                Toast.makeText(this, R.string.wifi_ip_settings_invalid_ip, Toast.LENGTH_LONG).show();
+                return false;
+            }
+            
+            preference.setSummary(value);
         }
         
-        preference.setSummary(value);
         return true;
     }
 
@@ -177,4 +237,14 @@
         }
     }
     
+    private void refreshMacAddress() {
+        WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+
+        Preference wifiMacAddressPref = findPreference(KEY_MAC_ADDRESS);
+        String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
+        wifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress) ? macAddress 
+                : getString(R.string.status_unavailable));
+    }
+    
 }
diff --git a/src/com/android/settings/wifi/WifiLayer.java b/src/com/android/settings/wifi/WifiLayer.java
index b29fd92..b0857d2 100644
--- a/src/com/android/settings/wifi/WifiLayer.java
+++ b/src/com/android/settings/wifi/WifiLayer.java
@@ -94,7 +94,7 @@
     private boolean mIsObtainingAddress;
 
     /**
-     * See {@link Settings.System#WIFI_NUM_OPEN_NETWORKS_KEPT}.
+     * See {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT}.
      */
     private int WIFI_NUM_OPEN_NETWORKS_KEPT;
     /**
@@ -229,8 +229,8 @@
         mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
         mIntentFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
         
-        WIFI_NUM_OPEN_NETWORKS_KEPT = Settings.System.getInt(mContext.getContentResolver(),
-                Settings.System.WIFI_NUM_OPEN_NETWORKS_KEPT, 10);
+        WIFI_NUM_OPEN_NETWORKS_KEPT = Settings.Secure.getInt(mContext.getContentResolver(),
+            Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, 10);
     }
     
     /**
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index ceb995b..c92ed7c 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -134,8 +134,8 @@
         
         mOpenNetworkNotificationsEnabled = (CheckBoxPreference) preferenceScreen
                 .findPreference(KEY_OPEN_NETWORK_NOTIFICATIONS_ENABLED);
-        mOpenNetworkNotificationsEnabled.setChecked(Settings.System.getInt(getContentResolver(),
-                Settings.System.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);        
+        mOpenNetworkNotificationsEnabled.setChecked(Settings.Secure.getInt(getContentResolver(),
+            Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);        
         
         mAddOtherNetwork = preferenceScreen.findPreference(KEY_ADD_OTHER_NETWORK);
         
@@ -323,9 +323,9 @@
         if (preference == mAddOtherNetwork) {
             showAddOtherNetworkDialog();
         } else if (preference == mOpenNetworkNotificationsEnabled) {
-            Settings.System.putInt(getContentResolver(),
-                    Settings.System.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
-                    mOpenNetworkNotificationsEnabled.isChecked() ? 1 : 0);
+            Settings.Secure.putInt(getContentResolver(),
+                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                mOpenNetworkNotificationsEnabled.isChecked() ? 1 : 0);
         } else if (preference instanceof AccessPointPreference) {
             AccessPointState state = ((AccessPointPreference) preference).getAccessPointState();
             showAccessPointDialog(state, AccessPointDialog.MODE_INFO);