auto import from //depot/cupcake/@135843
diff --git a/src/com/android/settings/ActivityPicker.java b/src/com/android/settings/ActivityPicker.java
new file mode 100644
index 0000000..47e005f
--- /dev/null
+++ b/src/com/android/settings/ActivityPicker.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * 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.LauncherActivity;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.view.View;
+import android.widget.ListView;
+
+/**
+ * Displays a list of all activities matching the incoming {@link Intent.EXTRA_INTENT}
+ * query, along with any applicable icons. 
+ */
+public class ActivityPicker extends LauncherActivity {
+    
+    @Override
+    protected Intent getTargetIntent() {
+        Intent intent = this.getIntent();
+        Intent targetIntent = new Intent(Intent.ACTION_MAIN, null);
+        targetIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        
+        // Use a custom title for this dialog, if provided
+        if (intent.hasExtra(Intent.EXTRA_TITLE)) {
+            String title = intent.getStringExtra(Intent.EXTRA_TITLE);
+            setTitle(title);
+        }
+        
+        Parcelable parcel = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+        if (parcel instanceof Intent) {
+            targetIntent = (Intent) parcel;
+        }
+        
+        return targetIntent;
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Intent intent = intentForPosition(position);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+}
diff --git a/src/com/android/settings/AirplaneModeEnabler.java b/src/com/android/settings/AirplaneModeEnabler.java
new file mode 100644
index 0000000..f105712
--- /dev/null
+++ b/src/com/android/settings/AirplaneModeEnabler.java
@@ -0,0 +1,118 @@
+/*
+ * 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 com.android.internal.telephony.PhoneStateIntentReceiver;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+
+public class AirplaneModeEnabler implements Preference.OnPreferenceChangeListener {
+
+    private final Context mContext;
+
+    private PhoneStateIntentReceiver mPhoneStateReceiver;
+    
+    private final CheckBoxPreference mCheckBoxPref;
+
+    private static final int EVENT_SERVICE_STATE_CHANGED = 3;
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_SERVICE_STATE_CHANGED:
+                    onAirplaneModeChanged();
+                    break;
+            }
+        }
+    };
+
+    public AirplaneModeEnabler(Context context, CheckBoxPreference airplaneModeCheckBoxPreference) {
+        
+        mContext = context;
+        mCheckBoxPref = airplaneModeCheckBoxPreference;
+        
+        airplaneModeCheckBoxPreference.setPersistent(false);
+    
+        mPhoneStateReceiver = new PhoneStateIntentReceiver(mContext, mHandler);
+        mPhoneStateReceiver.notifyServiceState(EVENT_SERVICE_STATE_CHANGED);
+    }
+
+    public void resume() {
+        
+        // This is the widget enabled state, not the preference toggled state
+        mCheckBoxPref.setEnabled(true);
+        mCheckBoxPref.setChecked(isAirplaneModeOn(mContext));
+
+        mPhoneStateReceiver.registerIntent();
+        mCheckBoxPref.setOnPreferenceChangeListener(this);
+    }
+    
+    public void pause() {
+        mPhoneStateReceiver.unregisterIntent();
+        mCheckBoxPref.setOnPreferenceChangeListener(null);
+    }
+    
+    static boolean isAirplaneModeOn(Context context) {
+        return Settings.System.getInt(context.getContentResolver(),
+                Settings.System.AIRPLANE_MODE_ON, 0) != 0;
+    }
+
+    private void setAirplaneModeOn(boolean enabling) {
+        
+        mCheckBoxPref.setEnabled(false);
+        mCheckBoxPref.setSummary(enabling ? R.string.airplane_mode_turning_on
+                : R.string.airplane_mode_turning_off);
+        
+        // Change the system setting
+        Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 
+                                enabling ? 1 : 0);
+        
+        // Post the intent
+        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", enabling);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Called when we've received confirmation that the airplane mode was set.
+     */
+    private void onAirplaneModeChanged() {
+        ServiceState serviceState = mPhoneStateReceiver.getServiceState();
+        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);
+    }
+    
+    /**
+     * Called when someone clicks on the checkbox preference.
+     */
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        setAirplaneModeOn((Boolean) newValue);
+        return true;
+    }
+
+}
diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java
new file mode 100644
index 0000000..f1fa2ef
--- /dev/null
+++ b/src/com/android/settings/ApnEditor.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2006 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.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.provider.Telephony;
+import com.android.internal.telephony.TelephonyProperties;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+
+
+public class ApnEditor extends PreferenceActivity 
+        implements SharedPreferences.OnSharedPreferenceChangeListener {
+    
+
+    private final static String TAG = ApnEditor.class.getSimpleName();
+    
+    private final static String SAVED_POS = "pos";
+    
+    private static final int MENU_DELETE = Menu.FIRST;
+    private static final int MENU_SAVE = Menu.FIRST + 1;
+    private static final int MENU_CANCEL = Menu.FIRST + 2;
+    
+    private static String sNotSet;
+    private EditTextPreference mName;
+    private EditTextPreference mApn;
+    private EditTextPreference mProxy;
+    private EditTextPreference mPort;
+    private EditTextPreference mUser;
+    private EditTextPreference mServer;
+    private EditTextPreference mPassword;
+    private EditTextPreference mMmsc;
+    private EditTextPreference mMcc;
+    private EditTextPreference mMnc;
+    private EditTextPreference mMmsProxy;
+    private EditTextPreference mMmsPort;
+    private EditTextPreference mApnType;
+    private String mCurMnc;
+    private String mCurMcc;
+    
+    private Uri mUri;
+    private Cursor mCursor;
+    private boolean mNewApn;
+    private boolean mFirstTime;
+    private Resources mRes;
+    
+    /**
+     * Standard projection for the interesting columns of a normal note.
+     */
+    private static final String[] sProjection = new String[] {
+            Telephony.Carriers._ID,     // 0
+            Telephony.Carriers.NAME,    // 1
+            Telephony.Carriers.APN,     // 2
+            Telephony.Carriers.PROXY,   // 3
+            Telephony.Carriers.PORT,    // 4
+            Telephony.Carriers.USER,    // 5
+            Telephony.Carriers.SERVER,  // 6
+            Telephony.Carriers.PASSWORD, // 7
+            Telephony.Carriers.MMSC, // 8
+            Telephony.Carriers.MCC, // 9
+            Telephony.Carriers.MNC, // 10
+            Telephony.Carriers.NUMERIC, // 11
+            Telephony.Carriers.MMSPROXY,// 12
+            Telephony.Carriers.MMSPORT, // 13
+            Telephony.Carriers.TYPE, // 14
+    };
+    
+    private static final int ID_INDEX = 0;
+    private static final int NAME_INDEX = 1;
+    private static final int APN_INDEX = 2;
+    private static final int PROXY_INDEX = 3;
+    private static final int PORT_INDEX = 4;
+    private static final int USER_INDEX = 5;
+    private static final int SERVER_INDEX = 6;
+    private static final int PASSWORD_INDEX = 7;
+    private static final int MMSC_INDEX = 8;
+    private static final int MCC_INDEX = 9;
+    private static final int MNC_INDEX = 10;
+    private static final int MMSPROXY_INDEX = 12;
+    private static final int MMSPORT_INDEX = 13;
+    private static final int TYPE_INDEX = 14;
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.apn_editor);
+
+        sNotSet = getResources().getString(R.string.apn_not_set);
+        mName = (EditTextPreference) findPreference("apn_name");
+        mApn = (EditTextPreference) findPreference("apn_apn");
+        mProxy = (EditTextPreference) findPreference("apn_http_proxy");
+        mPort = (EditTextPreference) findPreference("apn_http_port");
+        mUser = (EditTextPreference) findPreference("apn_user");
+        mServer = (EditTextPreference) findPreference("apn_server");
+        mPassword = (EditTextPreference) findPreference("apn_password");
+        mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy");
+        mMmsPort = (EditTextPreference) findPreference("apn_mms_port");
+        mMmsc = (EditTextPreference) findPreference("apn_mmsc");
+        mMcc = (EditTextPreference) findPreference("apn_mcc");
+        mMnc = (EditTextPreference) findPreference("apn_mnc");
+        mApnType = (EditTextPreference) findPreference("apn_type");
+        
+        mRes = getResources();
+        
+        final Intent intent = getIntent();
+        final String action = intent.getAction();
+
+        mFirstTime = icicle == null;
+        
+        if (action.equals(Intent.ACTION_EDIT)) {
+            mUri = intent.getData();
+        } else if (action.equals(Intent.ACTION_INSERT)) {
+            if (mFirstTime) {
+                mUri = getContentResolver().insert(intent.getData(), new ContentValues());
+            } else {
+                mUri = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, 
+                        icicle.getInt(SAVED_POS));
+            }
+            mNewApn = true;
+            // If we were unable to create a new note, then just finish
+            // this activity.  A RESULT_CANCELED will be sent back to the
+            // original activity if they requested a result.
+            if (mUri == null) {
+                Log.w(TAG, "Failed to insert new telephony provider into "
+                        + getIntent().getData());
+                finish();
+                return;
+            }
+            
+            // The new entry was created, so assume all will end well and
+            // set the result to be returned.
+            setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
+
+        } else {
+            finish();
+            return;
+        }
+
+        mCursor = managedQuery(mUri, sProjection, null, null);
+        mCursor.moveToFirst();
+        
+        fillUi();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+    }
+    
+    @Override
+    public void onPause() {
+        getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+        super.onPause();        
+    }
+    
+    private void fillUi() {
+        if (mFirstTime) {            
+            mFirstTime = false;
+            // Fill in all the values from the db in both text editor and summary
+            mName.setText(mCursor.getString(NAME_INDEX));
+            mApn.setText(mCursor.getString(APN_INDEX));
+            mProxy.setText(mCursor.getString(PROXY_INDEX));
+            mPort.setText(mCursor.getString(PORT_INDEX));
+            mUser.setText(mCursor.getString(USER_INDEX));
+            mServer.setText(mCursor.getString(SERVER_INDEX));
+            mPassword.setText(mCursor.getString(PASSWORD_INDEX));
+            mMmsProxy.setText(mCursor.getString(MMSPROXY_INDEX));
+            mMmsPort.setText(mCursor.getString(MMSPORT_INDEX));
+            mMmsc.setText(mCursor.getString(MMSC_INDEX));
+            mMcc.setText(mCursor.getString(MCC_INDEX));
+            mMnc.setText(mCursor.getString(MNC_INDEX));
+            mApnType.setText(mCursor.getString(TYPE_INDEX));
+            if (mNewApn) {
+                String numeric = 
+                    SystemProperties.get(TelephonyProperties.PROPERTY_SIM_OPERATOR_NUMERIC);
+                // MCC is first 3 chars and then in 2 - 3 chars of MNC
+                if (numeric != null && numeric.length() > 4) {
+                    // Country code
+                    String mcc = numeric.substring(0, 3);
+                    // Network code
+                    String mnc = numeric.substring(3);
+                    // Auto populate MNC and MCC for new entries, based on what SIM reports
+                    mMcc.setText(mcc);
+                    mMnc.setText(mnc);
+                    mCurMnc = mnc;
+                    mCurMcc = mcc;
+                }
+            }
+        }
+        
+        mName.setSummary(checkNull(mName.getText()));
+        mApn.setSummary(checkNull(mApn.getText()));
+        mProxy.setSummary(checkNull(mProxy.getText()));
+        mPort.setSummary(checkNull(mPort.getText()));
+        mUser.setSummary(checkNull(mUser.getText()));
+        mServer.setSummary(checkNull(mServer.getText()));
+        mPassword.setSummary(starify(mPassword.getText()));
+        mMmsProxy.setSummary(checkNull(mMmsProxy.getText()));
+        mMmsPort.setSummary(checkNull(mMmsPort.getText()));
+        mMmsc.setSummary(checkNull(mMmsc.getText()));
+        mMcc.setSummary(checkNull(mMcc.getText()));
+        mMnc.setSummary(checkNull(mMnc.getText()));
+        mApnType.setSummary(checkNull(mApnType.getText()));
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        // If it's a new APN, then cancel will delete the new entry in onPause
+        if (!mNewApn) {
+            menu.add(0, MENU_DELETE, 0, R.string.menu_delete)
+                .setIcon(android.R.drawable.ic_menu_delete);
+        }
+        menu.add(0, MENU_SAVE, 0, R.string.menu_save)
+            .setIcon(android.R.drawable.ic_menu_save);
+        menu.add(0, MENU_CANCEL, 0, R.string.menu_cancel)
+            .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case MENU_DELETE:
+            deleteApn();
+            return true;
+        case MENU_SAVE:
+            if (validateAndSave(false)) {
+                finish();
+            }
+            return true;
+        case MENU_CANCEL:
+            if (mNewApn) {
+                getContentResolver().delete(mUri, null, null);
+            }
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+    
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK: {
+                if (validateAndSave(false)) {
+                    finish();
+                }
+                return true;
+            }
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle icicle) {
+        super.onSaveInstanceState(icicle);
+        validateAndSave(true);
+        icicle.putInt(SAVED_POS, mCursor.getInt(ID_INDEX));
+    }
+    
+    /**
+     * Check the key fields' validity and save if valid.
+     * @param force save even if the fields are not valid, if the app is
+     *        being suspended
+     * @return true if the data was saved
+     */
+    private boolean validateAndSave(boolean force) {
+        String name = checkNotSet(mName.getText());
+        String apn = checkNotSet(mApn.getText());
+        String mcc = checkNotSet(mMcc.getText());
+        String mnc = checkNotSet(mMnc.getText());
+        
+        String errorMsg = null;
+        if (name.length() < 1) {
+            errorMsg = mRes.getString(R.string.error_name_empty);
+        } else if (apn.length() < 1) {
+            errorMsg = mRes.getString(R.string.error_apn_empty);
+        } else if (mcc.length() != 3) {
+            errorMsg = mRes.getString(R.string.error_mcc_not3);
+        } else if ((mnc.length() & 0xFFFE) != 2) {
+            errorMsg = mRes.getString(R.string.error_mnc_not23);
+        }
+        
+        if (errorMsg != null && !force) {
+            showErrorMessage(errorMsg);
+            return false;
+        }
+        
+        if (!mCursor.moveToFirst()) {
+            Log.w(TAG,
+                    "Could not go to the first row in the Cursor when saving data.");
+            return false;
+        }
+        
+        ContentValues values = new ContentValues();
+        
+        values.put(Telephony.Carriers.NAME, name);
+        values.put(Telephony.Carriers.APN, apn);
+        values.put(Telephony.Carriers.PROXY, checkNotSet(mProxy.getText()));
+        values.put(Telephony.Carriers.PORT, checkNotSet(mPort.getText()));
+        values.put(Telephony.Carriers.MMSPROXY, checkNotSet(mMmsProxy.getText()));
+        values.put(Telephony.Carriers.MMSPORT, checkNotSet(mMmsPort.getText()));
+        values.put(Telephony.Carriers.USER, checkNotSet(mUser.getText()));
+        values.put(Telephony.Carriers.SERVER, checkNotSet(mServer.getText()));
+        values.put(Telephony.Carriers.PASSWORD, checkNotSet(mPassword.getText()));
+        values.put(Telephony.Carriers.MMSC, checkNotSet(mMmsc.getText()));            
+        values.put(Telephony.Carriers.TYPE, checkNotSet(mApnType.getText()));
+
+        values.put(Telephony.Carriers.MCC, mcc);
+        values.put(Telephony.Carriers.MNC, mnc);
+        
+        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;
+    }
+
+    private void showErrorMessage(String message) {
+        new AlertDialog.Builder(this)
+            .setTitle(R.string.error_title)
+            .setMessage(message)
+            .setPositiveButton(android.R.string.ok, null)
+            .show();
+    }
+
+    private void deleteApn() {
+        getContentResolver().delete(mUri, null, null);
+        finish();
+    }
+    
+    private String starify(String value) {
+        if (value == null || value.length() == 0) {
+            return sNotSet;
+        } else {
+            char[] password = new char[value.length()];
+            for (int i = 0; i < password.length; i++) {
+                password[i] = '*';
+            }
+            return new String(password);
+        }
+    }
+    
+    private String checkNull(String value) {
+        if (value == null || value.length() == 0) {
+            return sNotSet;
+        } else {
+            return value;
+        }
+    }
+    
+    private String checkNotSet(String value) {
+        if (value == null || value.equals(sNotSet)) {
+            return "";
+        } else {
+            return value;
+        }
+    }
+    
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        Preference pref = findPreference(key);
+        if (pref != null) {
+            pref.setSummary(checkNull(sharedPreferences.getString(key, "")));
+        }
+    }
+}
diff --git a/src/com/android/settings/ApnSettings.java b/src/com/android/settings/ApnSettings.java
new file mode 100644
index 0000000..87e3412
--- /dev/null
+++ b/src/com/android/settings/ApnSettings.java
@@ -0,0 +1,229 @@
+/*
+ * 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.Dialog;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.provider.Telephony;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+public class ApnSettings extends PreferenceActivity {
+
+    public static final String EXTRA_POSITION = "position";
+    public static final String RESTORE_CARRIERS_URI =
+        "content://telephony/carriers/restore";
+
+    private static final int ID_INDEX = 0;
+    private static final int NAME_INDEX = 1;
+    private static final int APN_INDEX = 2;
+
+    private static final int MENU_NEW = Menu.FIRST;
+    private static final int MENU_RESTORE = Menu.FIRST + 1;
+
+    private static final int EVENT_RESTORE_DEFAULTAPN_START = 1;
+    private static final int EVENT_RESTORE_DEFAULTAPN_COMPLETE = 2;
+
+    private static final int DIALOG_RESTORE_DEFAULTAPN = 1001;
+
+    private static final Uri DEFAULTAPN_URI = Uri.parse(RESTORE_CARRIERS_URI);
+
+    private static boolean mRestoreDefaultApnMode;
+
+    private RestoreApnUiHandler mRestoreApnUiHandler;
+    private RestoreApnProcessHandler mRestoreApnProcessHandler;
+
+    private Cursor mCursor;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.apn_settings);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (!mRestoreDefaultApnMode) {
+            fillList();
+        } else {
+            showDialog(DIALOG_RESTORE_DEFAULTAPN);
+        }
+    }
+
+    private void fillList() {
+        mCursor = managedQuery(Telephony.Carriers.CONTENT_URI, new String[] {
+                "_id", "name", "apn"}, null, Telephony.Carriers.DEFAULT_SORT_ORDER);
+
+        PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
+        apnList.removeAll();
+
+        mCursor.moveToFirst();
+        while (!mCursor.isAfterLast()) {
+            String name = mCursor.getString(NAME_INDEX);
+            String apn = mCursor.getString(APN_INDEX);
+
+            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();
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        menu.add(0, MENU_NEW, 0,
+                getResources().getString(R.string.menu_new))
+                .setIcon(android.R.drawable.ic_menu_add);
+        menu.add(0, MENU_RESTORE, 0,
+                getResources().getString(R.string.menu_restore))
+                .setIcon(android.R.drawable.ic_menu_upload);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case MENU_NEW:
+            addNewApn();
+            return true;
+
+        case MENU_RESTORE:
+            restoreDefaultApn();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void addNewApn() {
+        startActivity(new Intent(Intent.ACTION_INSERT, Telephony.Carriers.CONTENT_URI));
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        int pos = Integer.parseInt(preference.getKey());
+        Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos);
+        startActivity(new Intent(Intent.ACTION_EDIT, url));
+        return true;
+    }
+
+    private boolean restoreDefaultApn() {
+        showDialog(DIALOG_RESTORE_DEFAULTAPN);
+        mRestoreDefaultApnMode = true;
+
+        if (mRestoreApnUiHandler == null) {
+            mRestoreApnUiHandler = new RestoreApnUiHandler();
+        }
+
+        if (mRestoreApnProcessHandler == null) {
+            HandlerThread restoreDefaultApnThread = new HandlerThread(
+                    "Restore default APN Handler: Process Thread");
+            restoreDefaultApnThread.start();
+            mRestoreApnProcessHandler = new RestoreApnProcessHandler(
+                    restoreDefaultApnThread.getLooper(), mRestoreApnUiHandler);
+        }
+
+        mRestoreApnProcessHandler
+                .sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START);
+        return true;
+    }
+
+    private class RestoreApnUiHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_RESTORE_DEFAULTAPN_COMPLETE:
+                    fillList();
+                    getPreferenceScreen().setEnabled(true);
+                    mRestoreDefaultApnMode = false;
+                    dismissDialog(DIALOG_RESTORE_DEFAULTAPN);
+                    Toast.makeText(
+                        ApnSettings.this,
+                        getResources().getString(
+                                R.string.restore_default_apn_completed),
+                        Toast.LENGTH_LONG).show();
+                    break;
+            }
+        }
+    }
+
+    private class RestoreApnProcessHandler extends Handler {
+        private Handler mRestoreApnUiHandler;
+
+        public RestoreApnProcessHandler(Looper looper, Handler restoreApnUiHandler) {
+            super(looper);
+            this.mRestoreApnUiHandler = restoreApnUiHandler;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_RESTORE_DEFAULTAPN_START:
+                    ContentResolver resolver = getContentResolver();
+                    resolver.delete(DEFAULTAPN_URI, null, null);                    
+                    mRestoreApnUiHandler
+                        .sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_COMPLETE);
+                    break;
+            }
+        }
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        if (id == DIALOG_RESTORE_DEFAULTAPN) {
+            ProgressDialog dialog = new ProgressDialog(this);
+            dialog.setMessage(getResources().getString(R.string.restore_default_apn));
+            dialog.setCancelable(false);
+            return dialog;
+        }
+        return null;
+    }
+
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        if (id == DIALOG_RESTORE_DEFAULTAPN) {
+            getPreferenceScreen().setEnabled(false);
+        }
+    }
+}
diff --git a/src/com/android/settings/ApplicationSettings.java b/src/com/android/settings/ApplicationSettings.java
new file mode 100644
index 0000000..85fe11f
--- /dev/null
+++ b/src/com/android/settings/ApplicationSettings.java
@@ -0,0 +1,98 @@
+/*
+ * 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.content.DialogInterface;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+
+public class ApplicationSettings extends PreferenceActivity implements
+        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;
+    
+    private DialogInterface mWarnInstallApps;
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.application_settings);
+
+        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
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        if (preference == mToggleAppInstallation) {
+            if (mToggleAppInstallation.isChecked()) {
+                mToggleAppInstallation.setChecked(false);
+                warnAppInstallation();
+            } else {
+                setNonMarketAppsAllowed(false);
+            }
+        }
+        
+        return super.onPreferenceTreeClick(preferenceScreen, preference);
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        if (dialog == mWarnInstallApps && which == DialogInterface.BUTTON1) {
+            setNonMarketAppsAllowed(true);
+            mToggleAppInstallation.setChecked(true);
+        }
+    }
+
+    private void setNonMarketAppsAllowed(boolean enabled) {
+        // Change the system setting
+        Settings.Secure.putInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, 
+                                enabled ? 1 : 0);
+    }
+    
+    private boolean isNonMarketAppsAllowed() {
+        return Settings.Secure.getInt(getContentResolver(), 
+                                      Settings.Secure.INSTALL_NON_MARKET_APPS, 0) > 0;
+    }
+
+    private void warnAppInstallation() {
+        mWarnInstallApps = new AlertDialog.Builder(this)
+                .setTitle(getString(R.string.error_title))
+                .setIcon(com.android.internal.R.drawable.ic_dialog_alert)
+                .setMessage(getResources().getString(R.string.install_all_warning))
+                .setPositiveButton(android.R.string.yes, this)
+                .setNegativeButton(android.R.string.no, null)
+                .show();
+    }
+    
+    
+}
diff --git a/src/com/android/settings/BandMode.java b/src/com/android/settings/BandMode.java
new file mode 100644
index 0000000..1297cad
--- /dev/null
+++ b/src/com/android/settings/BandMode.java
@@ -0,0 +1,217 @@
+package com.android.settings;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Handler;
+import android.os.AsyncResult;
+import android.util.Log;
+import android.content.DialogInterface;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.Window;
+import android.widget.ListView;
+import android.widget.ArrayAdapter;
+import android.widget.AdapterView;
+
+
+/**
+ * Radio Band Mode Selection Class
+ *
+ * It will query baseband about all available band modes and display them
+ * in screen. It will display all six band modes if the query failed.
+ *
+ * After user select one band, it will send the selection to baseband.
+ *
+ * It will alter user the result of select operation and exit, no matter success
+ * or not.
+ *
+ */
+public class BandMode extends Activity {
+    private static final String LOG_TAG = "phone";
+    private static final boolean DBG = false;
+
+    private static final int EVENT_BAND_SCAN_COMPLETED = 100;
+    private static final int EVENT_BAND_SELECTION_DONE = 200;
+
+    private static final String[] BAND_NAMES = new String[] {
+            "Automatic",
+            "EURO Band",
+            "USA Band",
+            "JAPAN Band",
+            "AUS Band",
+            "AUS2 Band"
+    };
+
+    private ListView mBandList;
+    private ArrayAdapter mBandListAdapter;
+    private BandListItem mTargetBand = null;
+    private DialogInterface mProgressPanel;
+
+    private Phone mPhone = null;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        
+        setContentView(R.layout.band_mode);
+
+        setTitle(getString(R.string.band_mode_title));
+        getWindow().setLayout(WindowManager.LayoutParams.FILL_PARENT,
+                                    WindowManager.LayoutParams.WRAP_CONTENT);
+
+        mPhone = PhoneFactory.getDefaultPhone();
+
+        mBandList = (ListView) findViewById(R.id.band);
+        mBandListAdapter = new ArrayAdapter<BandListItem>(this,
+                android.R.layout.simple_list_item_1);
+        mBandList.setAdapter(mBandListAdapter);
+        mBandList.setOnItemClickListener(mBandSelectionHandler);
+
+
+
+        loadBandList();
+    }
+
+    private AdapterView.OnItemClickListener mBandSelectionHandler =
+            new AdapterView.OnItemClickListener () {
+                public void onItemClick(AdapterView parent, View v,
+                        int position, long id) {
+
+                    getWindow().setFeatureInt(
+                            Window.FEATURE_INDETERMINATE_PROGRESS,
+                            Window.PROGRESS_VISIBILITY_ON);
+
+                    mTargetBand = (BandListItem) parent.getAdapter().getItem(position);
+
+                    if (DBG) log("Select band : " + mTargetBand.toString());
+
+                    Message msg =
+                            mHandler.obtainMessage(EVENT_BAND_SELECTION_DONE);
+                    mPhone.setBandMode(mTargetBand.getBand(), msg);
+                }
+            };
+
+    private class BandListItem {
+        private int mBandMode = Phone.BM_UNSPECIFIED;
+
+        public BandListItem(int bm) {
+            mBandMode = bm;
+        }
+
+        public int getBand() {
+            return mBandMode;
+        }
+
+        public String toString() {
+            return BAND_NAMES[mBandMode];
+        }
+    }
+
+    private void loadBandList() {
+        String str = getString(R.string.band_mode_loading);
+
+        if (DBG) log(str);
+
+
+        //ProgressDialog.show(this, null, str, true, true, null);
+        mProgressPanel = new AlertDialog.Builder(this)
+            .setMessage(str)
+            .show();
+
+        Message msg = mHandler.obtainMessage(EVENT_BAND_SCAN_COMPLETED);
+        mPhone.queryAvailableBandMode(msg);
+
+    }
+
+    private void bandListLoaded(AsyncResult result) {
+        if (DBG) log("network list loaded");
+
+        if (mProgressPanel != null) mProgressPanel.dismiss();
+
+        clearList();
+
+        boolean addBandSuccess = false;
+        BandListItem item;
+
+        if (result.result != null) {
+            int bands[] = (int[])result.result;
+            int size = bands[0];
+
+            if (size > 0) {
+                for (int i=1; i<size; i++) {
+                    item = new BandListItem(bands[i]);
+                    mBandListAdapter.add(item);
+                    if (DBG) log("Add " + item.toString());
+                }
+                addBandSuccess = true;
+            }
+        }
+
+        if (addBandSuccess == false) {
+            if (DBG) log("Error in query, add default list");
+            for (int i=0; i<Phone.BM_BOUNDARY; i++) {
+                item = new BandListItem(i);
+                mBandListAdapter.add(item);
+                if (DBG) log("Add default " + item.toString());
+            }
+        }
+        mBandList.requestFocus();
+    }
+
+    private void displayBandSelectionResult(Throwable ex) {
+        String status = getString(R.string.band_mode_set)
+                +" [" + mTargetBand.toString() + "] ";
+
+        if (ex != null) {
+            status = status + getString(R.string.band_mode_failed);
+        } else {
+            status = status + getString(R.string.band_mode_succeeded);
+        }
+
+        mProgressPanel = new AlertDialog.Builder(this)
+            .setMessage(status)
+            .setPositiveButton(android.R.string.ok, null).show();
+    }
+
+    private void clearList() {
+        while(mBandListAdapter.getCount() > 0) {
+            mBandListAdapter.remove(
+                    mBandListAdapter.getItem(0));
+        }
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "[BandsList] " + msg);
+    }
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+            switch (msg.what) {
+                case EVENT_BAND_SCAN_COMPLETED:
+                    ar = (AsyncResult) msg.obj;
+
+                    bandListLoaded(ar);
+                    break;
+
+                case EVENT_BAND_SELECTION_DONE:
+                    ar = (AsyncResult) msg.obj;
+
+                    getWindow().setFeatureInt(
+                            Window.FEATURE_INDETERMINATE_PROGRESS,
+                            Window.PROGRESS_VISIBILITY_OFF);
+
+                    displayBandSelectionResult(ar.exception);
+                    break;
+            }
+        }
+    };
+
+
+}
diff --git a/src/com/android/settings/BatteryInfo.java b/src/com/android/settings/BatteryInfo.java
new file mode 100644
index 0000000..f9962fa
--- /dev/null
+++ b/src/com/android/settings/BatteryInfo.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2006 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.widget.TextView;
+
+import com.android.internal.app.IBatteryStats;
+
+public class BatteryInfo extends Activity {
+    private TextView mStatus;
+    private TextView mLevel;
+    private TextView mScale;
+    private TextView mHealth;
+    private TextView mVoltage;
+    private TextView mTemperature;
+    private TextView mTechnology;
+    private TextView mUptime;
+    private TextView mAwakeBattery;
+    private TextView mAwakePlugged;
+    private TextView mScreenOn;
+    private IBatteryStats mBatteryStats;
+    private IPowerManager mScreenStats;
+    
+    private static final int EVENT_TICK = 1;
+    
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_TICK:
+                    updateBatteryStats();
+                    sendEmptyMessageDelayed(EVENT_TICK, 1000);
+                    break;
+            }
+        }
+    };
+
+    /**
+     * Format a number of tenths-units as a decimal string without using a
+     * conversion to float.  E.g. 347 -> "34.7"
+     */
+    private final String tenthsToFixedString(int x) {
+        int tens = x / 10;
+        return new String("" + tens + "." + (x - 10*tens));
+    }
+
+   /**
+    *Listens for intent broadcasts
+    */
+    private IntentFilter   mIntentFilter;
+
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+                int plugType = intent.getIntExtra("plugged", 0);
+
+                mLevel.setText("" + intent.getIntExtra("level", 0));
+                mScale.setText("" + intent.getIntExtra("scale", 0));
+                mVoltage.setText("" + intent.getIntExtra("voltage", 0) + " "
+                        + getString(R.string.battery_info_voltage_units));
+                mTemperature.setText("" + tenthsToFixedString(intent.getIntExtra("temperature", 0))
+                        + getString(R.string.battery_info_temperature_units));
+                mTechnology.setText("" + intent.getStringExtra("technology"));
+                
+                int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
+                String statusString;
+                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+                    statusString = getString(R.string.battery_info_status_charging);
+                    if (plugType > 0) {
+                        statusString = statusString + " " + getString(
+                                (plugType == BatteryManager.BATTERY_PLUGGED_AC)
+                                        ? R.string.battery_info_status_charging_ac
+                                        : R.string.battery_info_status_charging_usb);
+                    }
+                } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+                    statusString = getString(R.string.battery_info_status_discharging);
+                } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
+                    statusString = getString(R.string.battery_info_status_not_charging);
+                } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
+                    statusString = getString(R.string.battery_info_status_full);
+                } else {
+                    statusString = getString(R.string.battery_info_status_unknown);
+                }
+                mStatus.setText(statusString);
+
+                int health = intent.getIntExtra("health", BatteryManager.BATTERY_HEALTH_UNKNOWN);
+                String healthString;
+                if (health == BatteryManager.BATTERY_HEALTH_GOOD) {
+                    healthString = getString(R.string.battery_info_health_good);
+                } else if (health == BatteryManager.BATTERY_HEALTH_OVERHEAT) {
+                    healthString = getString(R.string.battery_info_health_overheat);
+                } else if (health == BatteryManager.BATTERY_HEALTH_DEAD) {
+                    healthString = getString(R.string.battery_info_health_dead);
+                } else if (health == BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE) {
+                    healthString = getString(R.string.battery_info_health_over_voltage);
+                } else if (health == BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE) {
+                    healthString = getString(R.string.battery_info_health_unspecified_failure);
+                } else {
+                    healthString = getString(R.string.battery_info_health_unknown);
+                }
+                mHealth.setText(healthString);
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.battery_info);
+
+        // create the IntentFilter that will be used to listen
+        // to battery status broadcasts
+        mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        mStatus = (TextView)findViewById(R.id.status);
+        mLevel = (TextView)findViewById(R.id.level);
+        mScale = (TextView)findViewById(R.id.scale);
+        mHealth = (TextView)findViewById(R.id.health);
+        mTechnology = (TextView)findViewById(R.id.technology);
+        mVoltage = (TextView)findViewById(R.id.voltage);
+        mTemperature = (TextView)findViewById(R.id.temperature);
+        mUptime = (TextView) findViewById(R.id.uptime);
+        mAwakeBattery = (TextView) findViewById(R.id.awakeBattery);
+        mAwakePlugged = (TextView) findViewById(R.id.awakePlugged);
+        mScreenOn = (TextView) findViewById(R.id.screenOn);
+        
+        // Get awake time plugged in and on battery
+        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+        mScreenStats = IPowerManager.Stub.asInterface(ServiceManager.getService(POWER_SERVICE));
+        mHandler.sendEmptyMessageDelayed(EVENT_TICK, 1000);
+        
+        registerReceiver(mIntentReceiver, mIntentFilter);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mHandler.removeMessages(EVENT_TICK);
+        
+        // we are no longer on the screen stop the observers
+        unregisterReceiver(mIntentReceiver);
+    }
+
+    private void updateBatteryStats() {
+        long uptime = SystemClock.elapsedRealtime();
+        mUptime.setText(DateUtils.formatElapsedTime(uptime / 1000));
+        
+        if (mBatteryStats != null) {
+            try {
+                long awakeTimeBattery = mBatteryStats.getAwakeTimeBattery() / 1000;
+                long awakeTimePluggedIn = mBatteryStats.getAwakeTimePlugged() / 1000;
+                mAwakeBattery.setText(DateUtils.formatElapsedTime(awakeTimeBattery / 1000) 
+                        + " (" + (100 * awakeTimeBattery / uptime) + "%)");
+                mAwakePlugged.setText(DateUtils.formatElapsedTime(awakeTimePluggedIn / 1000)
+                        + " (" + (100 * awakeTimePluggedIn / uptime) + "%)");
+            } catch (RemoteException re) {
+                mAwakeBattery.setText("Unknown");
+                mAwakePlugged.setText("Unknown");            
+            }
+        }
+        if (mScreenStats != null) {
+            try {
+                long screenOnTime = mScreenStats.getScreenOnTime();
+                mScreenOn.setText(DateUtils.formatElapsedTime(screenOnTime / 1000));
+            } catch (RemoteException re) {
+                mScreenOn.setText("Unknown");
+            }
+        }
+    }
+    
+}
diff --git a/src/com/android/settings/BrightnessPreference.java b/src/com/android/settings/BrightnessPreference.java
new file mode 100644
index 0000000..a9851cc
--- /dev/null
+++ b/src/com/android/settings/BrightnessPreference.java
@@ -0,0 +1,103 @@
+/*
+ * 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.os.RemoteException;
+import android.os.IHardwareService;
+import android.os.ServiceManager;
+import android.preference.SeekBarPreference;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.SeekBar;
+
+import java.util.Map;
+
+public class BrightnessPreference extends SeekBarPreference implements
+        SeekBar.OnSeekBarChangeListener {
+
+    private SeekBar mSeekBar;
+    
+    private int mOldBrightness;
+    
+    // Backlight range is from 0 - 255. Need to make sure that user
+    // doesn't set the backlight to 0 and get stuck
+    private static final int MINIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_DIM + 10;
+    private static final int MAXIMUM_BACKLIGHT = android.os.Power.BRIGHTNESS_ON;
+
+    public BrightnessPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        mSeekBar = getSeekBar(view);
+        mSeekBar.setOnSeekBarChangeListener(this);
+        mSeekBar.setMax(MAXIMUM_BACKLIGHT - MINIMUM_BACKLIGHT);
+        try {
+            mOldBrightness = Settings.System.getInt(getContext().getContentResolver(), 
+                Settings.System.SCREEN_BRIGHTNESS);
+        } catch (SettingNotFoundException snfe) {
+            mOldBrightness = MAXIMUM_BACKLIGHT;
+        }
+        mSeekBar.setProgress(mOldBrightness - MINIMUM_BACKLIGHT);
+    }
+
+    public void onProgressChanged(SeekBar seekBar, int progress,
+            boolean fromTouch) {
+        setBrightness(progress + MINIMUM_BACKLIGHT);
+    }
+
+    public void onStartTrackingTouch(SeekBar seekBar) {
+        // NA
+    }
+
+    public void onStopTrackingTouch(SeekBar seekBar) {
+        // NA
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        
+        if (positiveResult) {
+            Settings.System.putInt(getContext().getContentResolver(), 
+                    Settings.System.SCREEN_BRIGHTNESS,
+                    mSeekBar.getProgress() + MINIMUM_BACKLIGHT);
+        } else {
+            setBrightness(mOldBrightness);
+        }
+    }
+    
+    private void setBrightness(int brightness) {
+        try {
+            IHardwareService hardware = IHardwareService.Stub.asInterface(
+                    ServiceManager.getService("hardware"));
+            if (hardware != null) {
+                hardware.setScreenBacklight(brightness);
+            }
+        } catch (RemoteException doe) {
+            
+        }        
+    }
+}
+
diff --git a/src/com/android/settings/ChooseLockPattern.java b/src/com/android/settings/ChooseLockPattern.java
new file mode 100644
index 0000000..47fc07f
--- /dev/null
+++ b/src/com/android/settings/ChooseLockPattern.java
@@ -0,0 +1,497 @@
+/*
+ * 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 com.google.android.collect.Lists;
+
+import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import static com.android.internal.widget.LockPatternView.DisplayMode;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * If the user has a lock pattern set already, makes them confirm the existing one.
+ *
+ * Then, prompts the user to choose a lock pattern:
+ * - prompts for initial pattern
+ * - asks for confirmation / restart
+ * - saves chosen password when confirmed
+ */
+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;
+
+    // how long we wait to clear a wrong pattern
+    private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+    private static final int ID_EMPTY_MESSAGE = -1;
+
+
+    protected TextView mHeaderText;
+    protected LockPatternView mLockPatternView;
+    protected TextView mFooterText;
+    private TextView mFooterLeftButton;
+    private TextView mFooterRightButton;
+
+    protected List<LockPatternView.Cell> mChosenPattern = null;
+
+    protected LockPatternUtils mLockPatternUtils;
+
+    /**
+     * The patten used during the help screen to show how to draw a pattern.
+     */
+    private final List<LockPatternView.Cell> mAnimatePattern =
+            Collections.unmodifiableList(
+                Lists.newArrayList(
+                        LockPatternView.Cell.of(0, 0),
+                        LockPatternView.Cell.of(0, 1),
+                        LockPatternView.Cell.of(1, 1),
+                        LockPatternView.Cell.of(2, 1)
+                    ));
+
+
+    /**
+     * The pattern listener that responds according to a user choosing a new
+     * lock pattern.
+     */
+    protected LockPatternView.OnPatternListener mChooseNewLockPatternListener = new LockPatternView.OnPatternListener() {
+
+            public void onPatternStart() {
+                mLockPatternView.removeCallbacks(mClearPatternRunnable);
+                patternInProgress();
+            }
+
+            public void onPatternCleared() {
+                mLockPatternView.removeCallbacks(mClearPatternRunnable);
+            }
+
+            public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+                if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
+                    if (mChosenPattern == null) throw new IllegalStateException("null chosen pattern in stage 'need to confirm");
+                    if (mChosenPattern.equals(pattern)) {
+                        updateStage(Stage.ChoiceConfirmed);
+                    } else {
+                        updateStage(Stage.ConfirmWrong);
+                    }
+                } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
+                    if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+                        updateStage(Stage.ChoiceTooShort);
+                    } else {
+                        mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern);
+                        updateStage(Stage.FirstChoiceValid);
+                    }
+                } else {
+                    throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
+                            + "entering the pattern.");
+                }
+            }
+
+            private void patternInProgress() {
+                mHeaderText.setText(R.string.lockpattern_recording_inprogress);
+                mFooterText.setText("");
+                mFooterLeftButton.setEnabled(false);
+                mFooterRightButton.setEnabled(false);
+            }
+     };
+
+
+    /**
+     * The states of the left footer button.
+     */
+    enum LeftButtonMode {
+        Cancel(R.string.cancel, true),
+        CancelDisabled(R.string.cancel, false),
+        Retry(R.string.lockpattern_retry_button_text, true),
+        RetryDisabled(R.string.lockpattern_retry_button_text, false),
+        Gone(ID_EMPTY_MESSAGE, false);
+
+
+        /**
+         * @param text The displayed text for this mode.
+         * @param enabled Whether the button should be enabled.
+         */
+        LeftButtonMode(int text, boolean enabled) {
+            this.text = text;
+            this.enabled = enabled;
+        }
+
+        final int text;
+        final boolean enabled;
+    }
+
+    /**
+     * The states of the right button.
+     */
+    enum RightButtonMode {
+        Continue(R.string.lockpattern_continue_button_text, true),
+        ContinueDisabled(R.string.lockpattern_continue_button_text, false),
+        Confirm(R.string.lockpattern_confirm_button_text, true),
+        ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
+        Ok(android.R.string.ok, true);
+
+        /**
+         * @param text The displayed text for this mode.
+         * @param enabled Whether the button should be enabled.
+         */
+        RightButtonMode(int text, boolean enabled) {
+            this.text = text;
+            this.enabled = enabled;
+        }
+
+        final int text;
+        final boolean enabled;
+    }
+
+    /**
+     * Keep track internally of where the user is in choosing a pattern.
+     */
+    protected enum Stage {
+
+        Introduction(
+                R.string.lockpattern_recording_intro_header,
+                LeftButtonMode.Cancel, RightButtonMode.ContinueDisabled,
+                R.string.lockpattern_recording_intro_footer, true),
+        HelpScreen(
+                R.string.lockpattern_settings_help_how_to_record,
+                LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
+        ChoiceTooShort(
+                R.string.lockpattern_recording_incorrect_too_short,
+                LeftButtonMode.Retry, RightButtonMode.ContinueDisabled,
+                ID_EMPTY_MESSAGE, true),
+        FirstChoiceValid(
+                R.string.lockpattern_pattern_entered_header,
+                LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false),
+        NeedToConfirm(
+                R.string.lockpattern_need_to_confirm,
+                LeftButtonMode.CancelDisabled, RightButtonMode.ConfirmDisabled,
+                ID_EMPTY_MESSAGE, true),
+        ConfirmWrong(
+                R.string.lockpattern_need_to_unlock_wrong,
+                LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled,
+                ID_EMPTY_MESSAGE, true),
+        ChoiceConfirmed(
+                R.string.lockpattern_pattern_confirmed_header,
+                LeftButtonMode.Cancel, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false);
+
+
+        /**
+         * @param headerMessage The message displayed at the top.
+         * @param leftMode The mode of the left button.
+         * @param rightMode The mode of the right button.
+         * @param footerMessage The footer message.
+         * @param patternEnabled Whether the pattern widget is enabled.
+         */
+        Stage(int headerMessage,
+                LeftButtonMode leftMode,
+                RightButtonMode rightMode,
+                int footerMessage, boolean patternEnabled) {
+            this.headerMessage = headerMessage;
+            this.leftMode = leftMode;
+            this.rightMode = rightMode;
+            this.footerMessage = footerMessage;
+            this.patternEnabled = patternEnabled;
+        }
+
+        final int headerMessage;
+        final LeftButtonMode leftMode;
+        final RightButtonMode rightMode;
+        final int footerMessage;
+        final boolean patternEnabled;
+    }
+
+    private Stage mUiStage = Stage.Introduction;
+
+    private Runnable mClearPatternRunnable = new Runnable() {
+        public void run() {
+            mLockPatternView.clearPattern();
+        }
+    };
+
+    private static final String KEY_UI_STAGE = "uiStage";
+    private static final String KEY_PATTERN_CHOICE = "chosenPattern";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mLockPatternUtils = new LockPatternUtils(getContentResolver());
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+        setupViews();
+        
+        // make it so unhandled touch events within the unlock screen go to the
+        // lock pattern view.
+        final LinearLayoutWithDefaultTouchRecepient topLayout
+                = (LinearLayoutWithDefaultTouchRecepient) findViewById(
+                R.id.topLayout);
+        topLayout.setDefaultTouchRecepient(mLockPatternView);
+
+        if (savedInstanceState == null) {
+            // first launch
+            updateStage(Stage.Introduction);
+            if (mLockPatternUtils.savedPatternExists()) {
+                confirmPattern();
+            } 
+        } else {
+            // restore from previous state
+            final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE);
+            if (patternString != null) {
+                mChosenPattern = LockPatternUtils.stringToPattern(patternString);
+            }
+            updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
+        }
+    }
+    
+    /**
+     * Keep all "find view" related stuff confined to this function since in
+     * case someone needs to subclass and customize.
+     */
+    protected void setupViews() {
+        setContentView(R.layout.choose_lock_pattern);
+        
+        mHeaderText = (TextView) findViewById(R.id.headerText);
+
+        mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
+        mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
+        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());        
+
+        mFooterText = (TextView) findViewById(R.id.footerText);
+
+        mFooterLeftButton = (TextView) findViewById(R.id.footerLeftButton);
+        mFooterRightButton = (TextView) findViewById(R.id.footerRightButton);
+
+        mFooterLeftButton.setOnClickListener(this);
+        mFooterRightButton.setOnClickListener(this);
+    }
+
+    public void onClick(View v) {
+        if (v == mFooterLeftButton) {
+            if (mUiStage.leftMode == LeftButtonMode.Retry) {
+                mChosenPattern = null;
+                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 " +
+                    mUiStage + " doesn't make sense");
+            }
+        } else if (v == mFooterRightButton) {
+
+            if (mUiStage.rightMode == RightButtonMode.Continue) {
+                if (mUiStage != Stage.FirstChoiceValid) {
+                    throw new IllegalStateException("expected ui stage " + Stage.FirstChoiceValid
+                            + " when button is " + RightButtonMode.Continue);
+                }
+                updateStage(Stage.NeedToConfirm);
+            } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
+                if (mUiStage != Stage.ChoiceConfirmed) {
+                    throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
+                            + " when button is " + RightButtonMode.Confirm);
+                }
+                saveChosenPatternAndFinish();
+            } else if (mUiStage.rightMode == RightButtonMode.Ok) {
+                if (mUiStage != Stage.HelpScreen) {
+                    throw new IllegalStateException("Help screen is only mode with ok button, but " +
+                            "stage is " + mUiStage);
+                }
+                mLockPatternView.clearPattern();
+                mLockPatternView.setDisplayMode(DisplayMode.Correct);
+                updateStage(Stage.Introduction);
+            }
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
+            if (mUiStage == Stage.HelpScreen) {
+                updateStage(Stage.Introduction);
+                return true;
+            }
+        }
+        if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
+            updateStage(Stage.HelpScreen);
+            return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    /**
+     * Launch screen to confirm the existing lock pattern.
+     * @see #onActivityResult(int, int, android.content.Intent)
+     */
+    protected void confirmPattern() {
+        final Intent intent = new Intent();
+        intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern");
+        startActivityForResult(intent, 55);
+    }
+
+    /**
+     * @see #confirmPattern
+     */
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode,
+            Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode != 55) {
+            return;
+        }
+
+        if (resultCode != Activity.RESULT_OK) {
+            setResult(RESULT_FINISHED);
+            finish();
+        }
+        updateStage(Stage.Introduction);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
+        if (mChosenPattern != null) {
+            outState.putString(KEY_PATTERN_CHOICE, LockPatternUtils.patternToString(mChosenPattern));
+        }
+    }
+
+
+    /**
+     * Updates the messages and buttons appropriate to what stage the user
+     * is at in choosing a view.  This doesn't handle clearing out the pattern;
+     * the pattern is expected to be in the right state.
+     * @param stage
+     */
+    protected void updateStage(Stage stage) {
+
+        mUiStage = stage;
+
+        // header text, footer text, visibility and 
+        // enabled state all known from the stage
+        if (stage == Stage.ChoiceTooShort) {
+            mHeaderText.setText(
+                    getResources().getString(
+                            stage.headerMessage,
+                            LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
+        } else {
+            mHeaderText.setText(stage.headerMessage);
+        }
+        if (stage.footerMessage == ID_EMPTY_MESSAGE) {
+            mFooterText.setText("");
+        } else {
+            mFooterText.setText(stage.footerMessage);
+        }
+
+        if (stage.leftMode == LeftButtonMode.Gone) {
+            mFooterLeftButton.setVisibility(View.GONE);
+        } else {
+            mFooterLeftButton.setVisibility(View.VISIBLE);
+            mFooterLeftButton.setText(stage.leftMode.text);
+            mFooterLeftButton.setEnabled(stage.leftMode.enabled);
+        }
+
+        mFooterRightButton.setText(stage.rightMode.text);
+        mFooterRightButton.setEnabled(stage.rightMode.enabled);
+
+        // same for whether the patten is enabled
+        if (stage.patternEnabled) {
+            mLockPatternView.enableInput();
+        } else {
+            mLockPatternView.disableInput();
+        }
+
+        // the rest of the stuff varies enough that it is easier just to handle
+        // on a case by case basis.
+        mLockPatternView.setDisplayMode(DisplayMode.Correct);
+
+        switch (mUiStage) {
+            case Introduction:
+                mLockPatternView.clearPattern();
+                break;
+            case HelpScreen:
+                mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
+                break;
+            case ChoiceTooShort:
+                mLockPatternView.setDisplayMode(DisplayMode.Wrong);
+                postClearPatternRunnable();
+                break;
+            case FirstChoiceValid:
+                break;
+            case NeedToConfirm:
+                mLockPatternView.clearPattern();
+                break;
+            case ConfirmWrong:
+                mLockPatternView.setDisplayMode(DisplayMode.Wrong);
+                postClearPatternRunnable();
+                break;
+            case ChoiceConfirmed:
+                break;
+        }
+    }
+
+
+    // clear the wrong pattern unless they have started a new one
+    // already
+    private void postClearPatternRunnable() {
+        mLockPatternView.removeCallbacks(mClearPatternRunnable);
+        mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
+    }
+
+    private void saveChosenPatternAndFinish() {
+        boolean patternExistedBefore = mLockPatternUtils.savedPatternExists();
+        mLockPatternUtils.saveLockPattern(mChosenPattern);
+
+        // if setting pattern for first time, enable the lock gesture.  otherwise,
+        // keep the user's setting.
+        if (!patternExistedBefore) {
+            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
new file mode 100644
index 0000000..77517b9
--- /dev/null
+++ b/src/com/android/settings/ChooseLockPatternExample.java
@@ -0,0 +1,104 @@
+/*
+ * 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.Activity;
+import android.content.Intent;
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+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;
+    private View mSkipButton;
+    private View mImageView;
+    private AnimationDrawable mAnimation;
+    private Handler mHandler = new Handler();
+    private Runnable mRunnable = new Runnable() {
+        public void run() {
+            startAnimation(mAnimation);
+        }
+    };
+   
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.choose_lock_pattern_example);
+        initViews();
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mHandler.postDelayed(mRunnable, START_DELAY);
+    }
+    
+    @Override
+    protected void onPause() {
+        super.onPause();
+        stopAnimation(mAnimation);
+    }
+    
+    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);
+            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);
+        
+        mSkipButton = findViewById(R.id.skip_button);
+        mSkipButton.setOnClickListener(this);
+        
+        mImageView = (ImageView) findViewById(R.id.lock_anim);
+        mImageView.setBackgroundResource(R.drawable.lock_anim);
+        mImageView.setOnClickListener(this);
+        mAnimation = (AnimationDrawable) mImageView.getBackground();
+    }
+    
+    protected void startAnimation(final AnimationDrawable animation) {
+        if (animation != null && !animation.isRunning()) {
+            animation.run();
+        }
+    }
+   
+    protected void stopAnimation(final AnimationDrawable animation) {
+        if (animation != null && animation.isRunning()) animation.stop();
+    }
+}
+
diff --git a/src/com/android/settings/ChooseLockPatternTutorial.java b/src/com/android/settings/ChooseLockPatternTutorial.java
new file mode 100644
index 0000000..a0a878a
--- /dev/null
+++ b/src/com/android/settings/ChooseLockPatternTutorial.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.internal.widget.LockPatternUtils;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+public class ChooseLockPatternTutorial extends Activity implements View.OnClickListener {
+    private static final int REQUESTCODE_EXAMPLE = 1;
+    
+    private View mNextButton;
+    private View mSkipButton;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Don't show the tutorial if the user has seen it before.
+        LockPatternUtils lockPatternUtils = new LockPatternUtils(getContentResolver());
+        if (savedInstanceState == null && lockPatternUtils.savedPatternExists()) {
+            Intent intent = new Intent();
+            intent.setClassName("com.android.settings", "com.android.settings.ChooseLockPattern");
+            startActivity(intent);
+            finish();
+        } else {
+            initViews();
+        }
+    }
+    
+    private void initViews() {
+        setContentView(R.layout.choose_lock_pattern_tutorial);
+        mNextButton = findViewById(R.id.next_button);
+        mNextButton.setOnClickListener(this);
+        mSkipButton = findViewById(R.id.skip_button);
+        mSkipButton.setOnClickListener(this);
+    }
+
+    public void onClick(View v) {
+        if (v == mSkipButton) {
+            // Canceling, so finish all
+            setResult(ChooseLockPattern.RESULT_FINISHED);
+            finish();
+        } else if (v == mNextButton) {
+            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/ConfirmLockPattern.java b/src/com/android/settings/ConfirmLockPattern.java
new file mode 100644
index 0000000..44baccc
--- /dev/null
+++ b/src/com/android/settings/ConfirmLockPattern.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;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.view.Window;
+
+import java.util.List;
+
+/**
+ * Launch this when you want the user to confirm their lock pattern.
+ *
+ * Sets an activity result of {@link Activity#RESULT_OK} when the user
+ * successfully confirmed their pattern.
+ */
+public class ConfirmLockPattern extends Activity {
+
+    /**
+     * Names of {@link CharSequence} fields within the originating {@link Intent}
+     * that are used to configure the keyguard confirmation view's labeling.
+     * The view will use the system-defined resource strings for any labels that
+     * the caller does not supply.
+     */
+    public static final String HEADER_TEXT = "com.android.settings.ConfirmLockPattern.header";
+    public static final String FOOTER_TEXT = "com.android.settings.ConfirmLockPattern.footer";
+    public static final String HEADER_WRONG_TEXT = "com.android.settings.ConfirmLockPattern.header_wrong";
+    public static final String FOOTER_WRONG_TEXT = "com.android.settings.ConfirmLockPattern.footer_wrong";
+
+    // how long we wait to clear a wrong pattern
+    private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
+
+    private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts";
+
+    private LockPatternView mLockPatternView;
+    private LockPatternUtils mLockPatternUtils;
+    private int mNumWrongConfirmAttempts;
+    private CountDownTimer mCountdownTimer;
+
+    private TextView mHeaderTextView;
+    private TextView mFooterTextView;
+
+    // caller-supplied text for various prompts
+    private CharSequence mHeaderText;
+    private CharSequence mFooterText;
+    private CharSequence mHeaderWrongText;
+    private CharSequence mFooterWrongText;
+
+
+    private enum Stage {
+        NeedToUnlock,
+        NeedToUnlockWrong,
+        LockedOut
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mLockPatternUtils = new LockPatternUtils(getContentResolver());
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.confirm_lock_pattern);
+
+        mHeaderTextView = (TextView) findViewById(R.id.headerText);
+        mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
+        mFooterTextView = (TextView) findViewById(R.id.footerText);
+
+        // make it so unhandled touch events within the unlock screen go to the
+        // lock pattern view.
+        final LinearLayoutWithDefaultTouchRecepient topLayout
+                = (LinearLayoutWithDefaultTouchRecepient) findViewById(
+                R.id.topLayout);
+        topLayout.setDefaultTouchRecepient(mLockPatternView);        
+
+        Intent intent = getIntent();
+        if (intent != null) {
+            mHeaderText = intent.getCharSequenceExtra(HEADER_TEXT);
+            mFooterText = intent.getCharSequenceExtra(FOOTER_TEXT);
+            mHeaderWrongText = intent.getCharSequenceExtra(HEADER_WRONG_TEXT);
+            mFooterWrongText = intent.getCharSequenceExtra(FOOTER_WRONG_TEXT);
+        }
+
+        mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
+        mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
+        updateStage(Stage.NeedToUnlock);
+
+        if (savedInstanceState != null) {
+            mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
+        } else {
+            // on first launch, if no lock pattern is set, then finish with
+            // success (don't want user to get stuck confirming something that
+            // doesn't exist).
+            if (!mLockPatternUtils.savedPatternExists()) {
+                setResult(RESULT_OK);
+                finish();
+            }
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        // deliberately not calling super since we are managing this in full
+        outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        if (mCountdownTimer != null) {
+            mCountdownTimer.cancel();
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // if the user is currently locked out, enforce it.
+        long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
+        if (deadline != 0) {
+            handleAttemptLockout(deadline);
+        }
+    }
+
+    private void updateStage(Stage stage) {
+
+        switch (stage) {
+            case NeedToUnlock:
+                if (mHeaderText != null) {
+                    mHeaderTextView.setText(mHeaderText);
+                } else {
+                    mHeaderTextView.setText(R.string.lockpattern_need_to_unlock);
+                }
+                if (mFooterText != null) {
+                    mFooterTextView.setText(mFooterText);
+                } else {
+                    mFooterTextView.setText(R.string.lockpattern_need_to_unlock_footer);
+                }
+                
+                mLockPatternView.setEnabled(true);
+                mLockPatternView.enableInput();
+                break;
+            case NeedToUnlockWrong:
+                if (mHeaderWrongText != null) {
+                    mHeaderTextView.setText(mHeaderWrongText);
+                } else {
+                    mHeaderTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
+                }
+                if (mFooterWrongText != null) {
+                    mFooterTextView.setText(mFooterWrongText);
+                } else {
+                    mFooterTextView.setText(R.string.lockpattern_need_to_unlock_wrong_footer);
+                }
+                
+                mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
+                mLockPatternView.setEnabled(true);
+                mLockPatternView.enableInput();
+                break;
+            case LockedOut:
+                mLockPatternView.clearPattern();
+                // enabled = false means: disable input, and have the
+                // appearance of being disabled.
+                mLockPatternView.setEnabled(false); // appearance of being disabled
+                break;
+        }
+    }
+
+    private Runnable mClearPatternRunnable = new Runnable() {
+        public void run() {
+            mLockPatternView.clearPattern();
+        }
+    };
+
+    // clear the wrong pattern unless they have started a new one
+    // already
+    private void postClearPatternRunnable() {
+        mLockPatternView.removeCallbacks(mClearPatternRunnable);
+        mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
+    }
+
+    /**
+     * The pattern listener that responds according to a user confirming
+     * an existing lock pattern.
+     */
+    private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener = new LockPatternView.OnPatternListener()  {
+
+        public void onPatternStart() {
+            mLockPatternView.removeCallbacks(mClearPatternRunnable);
+        }
+
+        public void onPatternCleared() {
+            mLockPatternView.removeCallbacks(mClearPatternRunnable);
+        }
+
+        public void onPatternDetected(List<LockPatternView.Cell> pattern) {
+            if (mLockPatternUtils.checkPattern(pattern)) {
+                setResult(RESULT_OK);
+                finish();
+            } else {
+                if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL &&
+                        ++mNumWrongConfirmAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
+                    long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
+                    handleAttemptLockout(deadline);
+                } else {
+                    updateStage(Stage.NeedToUnlockWrong);
+                    postClearPatternRunnable();
+                }
+            }
+        }
+    };
+
+
+    private void handleAttemptLockout(long elapsedRealtimeDeadline) {
+        updateStage(Stage.LockedOut);
+        long elapsedRealtime = SystemClock.elapsedRealtime();
+        mCountdownTimer = new CountDownTimer(
+                elapsedRealtimeDeadline - elapsedRealtime,
+                LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
+
+            @Override
+            public void onTick(long millisUntilFinished) {
+                mHeaderTextView.setText(R.string.lockpattern_too_many_failed_confirmation_attempts_header);
+                final int secondsCountdown = (int) (millisUntilFinished / 1000);
+                mFooterTextView.setText(getString(
+                        R.string.lockpattern_too_many_failed_confirmation_attempts_footer,
+                        secondsCountdown));
+            }
+
+            @Override
+            public void onFinish() {
+                mNumWrongConfirmAttempts = 0;
+                updateStage(Stage.NeedToUnlock);
+            }
+        }.start();
+    }
+}
diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java
new file mode 100644
index 0000000..e78215a
--- /dev/null
+++ b/src/com/android/settings/DateTimeSettings.java
@@ -0,0 +1,366 @@
+/*
+ * 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.Dialog;
+import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+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;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class DateTimeSettings 
+        extends PreferenceActivity 
+        implements OnSharedPreferenceChangeListener,
+                TimePickerDialog.OnTimeSetListener , DatePickerDialog.OnDateSetListener {
+
+    private static final String HOURS_12 = "12";
+    private static final String HOURS_24 = "24";
+    
+    private Calendar mDummyDate;
+    private static final String KEY_DATE_FORMAT = "date_format";
+    private static final String KEY_AUTO_TIME = "auto_time";
+
+    private static final int DIALOG_DATEPICKER = 0;
+    private static final int DIALOG_TIMEPICKER = 1;
+    
+    private CheckBoxPreference mAutoPref;
+    private Preference mTimePref;
+    private Preference mTime24Pref;
+    private Preference mTimeZone;
+    private Preference mDatePref;
+    private ListPreference mDateFormat;
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        addPreferencesFromResource(R.xml.date_time_prefs);
+        
+        initUI();        
+    }
+    
+    private void initUI() {
+        boolean autoEnabled = getAutoState();
+
+        mDummyDate = Calendar.getInstance();
+        mDummyDate.set(mDummyDate.get(Calendar.YEAR), 11, 31, 13, 0, 0);
+        
+        mAutoPref = (CheckBoxPreference) findPreference(KEY_AUTO_TIME);
+        mAutoPref.setChecked(autoEnabled);
+        mTimePref = findPreference("time");
+        mTime24Pref = findPreference("24 hour");
+        mTimeZone = findPreference("timezone");
+        mDatePref = findPreference("date");
+        mDateFormat = (ListPreference) findPreference(KEY_DATE_FORMAT);
+        
+        int currentFormatIndex = -1;
+        String [] dateFormats = getResources().getStringArray(R.array.date_format_values);
+        String [] formattedDates = new String[dateFormats.length];
+        String currentFormat = getDateFormat();
+        // Initialize if DATE_FORMAT is not set in the system settings
+        // This can happen after a factory reset (or data wipe)
+        if (currentFormat == null) {
+            currentFormat = getResources().getString(R.string.default_date_format);
+            setDateFormat(currentFormat);
+        }
+        for (int i = 0; i < formattedDates.length; i++) {
+            formattedDates[i] = DateFormat.format(dateFormats[i], mDummyDate).toString();
+            if (currentFormat.equals(dateFormats[i])) currentFormatIndex = i;
+        }
+        
+        mDateFormat.setEntries(formattedDates);
+        mDateFormat.setEntryValues(R.array.date_format_values);
+        mDateFormat.setValue(currentFormat);
+        
+        mTimePref.setEnabled(!autoEnabled);
+        mDatePref.setEnabled(!autoEnabled);
+        mTimeZone.setEnabled(!autoEnabled);
+
+        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);        
+    }
+
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        ((CheckBoxPreference)mTime24Pref).setChecked(is24Hour());
+
+        // Register for time ticks and other reasons for time change
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_TIME_TICK);
+        filter.addAction(Intent.ACTION_TIME_CHANGED);
+        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        registerReceiver(mIntentReceiver, filter, null, null);
+        
+        updateTimeAndDateDisplay();
+    }
+
+    @Override 
+    protected void onPause() {
+        super.onPause();
+        unregisterReceiver(mIntentReceiver);
+    }
+    
+    private void updateTimeAndDateDisplay() {
+        java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(this);
+        Date now = Calendar.getInstance().getTime();
+        Date dummyDate = mDummyDate.getTime();
+        mTimePref.setSummary(DateFormat.getTimeFormat(this).format(now));
+        mTimeZone.setSummary(getTimeZoneText());
+        mDatePref.setSummary(shortDateFormat.format(now));
+        mDateFormat.setSummary(shortDateFormat.format(dummyDate));
+    }
+
+    public void onDateSet(DatePicker view, int year, int month, int day) {
+        Calendar c = Calendar.getInstance();
+
+        c.set(Calendar.YEAR, year);
+        c.set(Calendar.MONTH, month);
+        c.set(Calendar.DAY_OF_MONTH, day);
+        long when = c.getTimeInMillis();
+
+        if (when / 1000 < Integer.MAX_VALUE) {
+            SystemClock.setCurrentTimeMillis(when);
+        }
+        updateTimeAndDateDisplay();
+    }
+
+    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
+        Calendar c = Calendar.getInstance();
+
+        c.set(Calendar.HOUR_OF_DAY, hourOfDay);
+        c.set(Calendar.MINUTE, minute);
+        long when = c.getTimeInMillis();
+
+        if (when / 1000 < Integer.MAX_VALUE) {
+            SystemClock.setCurrentTimeMillis(when);
+        }
+        updateTimeAndDateDisplay();
+        timeUpdated();
+    }
+
+    public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
+        if (key.equals(KEY_DATE_FORMAT)) {
+            String format = preferences.getString(key, 
+                    getResources().getString(R.string.default_date_format));
+            Settings.System.putString(getContentResolver(), 
+                    Settings.System.DATE_FORMAT, format);
+            updateTimeAndDateDisplay();
+        } else if (key.equals(KEY_AUTO_TIME)) {
+            boolean autoEnabled = preferences.getBoolean(key, true);
+            Settings.System.putInt(getContentResolver(), 
+                    Settings.System.AUTO_TIME, 
+                    autoEnabled ? 1 : 0);
+            mTimePref.setEnabled(!autoEnabled);
+            mDatePref.setEnabled(!autoEnabled);
+            mTimeZone.setEnabled(!autoEnabled);
+        }
+    }
+
+    @Override
+    public Dialog onCreateDialog(int id) {
+        Dialog d;
+
+        switch (id) {
+        case DIALOG_DATEPICKER: {
+            final Calendar calendar = Calendar.getInstance();
+            d = new DatePickerDialog(
+                this,
+                this,
+                calendar.get(Calendar.YEAR),
+                calendar.get(Calendar.MONTH),
+                calendar.get(Calendar.DAY_OF_MONTH));
+            d.setTitle(getResources().getString(R.string.date_time_changeDate_text));
+            break;
+        }
+        case DIALOG_TIMEPICKER: {
+            final Calendar calendar = Calendar.getInstance();
+            d = new TimePickerDialog(
+                    this,
+                    this,
+                    calendar.get(Calendar.HOUR_OF_DAY),
+                    calendar.get(Calendar.MINUTE),
+                    DateFormat.is24HourFormat(this));
+            d.setTitle(getResources().getString(R.string.date_time_changeTime_text));
+            break;
+        }
+        default:
+            d = null;
+            break;
+        }
+
+        return d;
+    }
+
+    @Override
+    public void onPrepareDialog(int id, Dialog d) {
+        switch (id) {
+        case DIALOG_DATEPICKER: {
+            DatePickerDialog datePicker = (DatePickerDialog)d;
+            final Calendar calendar = Calendar.getInstance();
+            datePicker.updateDate(
+                    calendar.get(Calendar.YEAR),
+                    calendar.get(Calendar.MONTH),
+                    calendar.get(Calendar.DAY_OF_MONTH));
+            break;
+        }
+        case DIALOG_TIMEPICKER: {
+            TimePickerDialog timePicker = (TimePickerDialog)d;
+            final Calendar calendar = Calendar.getInstance();
+            timePicker.updateTime(
+                    calendar.get(Calendar.HOUR_OF_DAY),
+                    calendar.get(Calendar.MINUTE));
+            break;
+        }
+        default:
+            break;
+        }
+    }
+    
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        if (preference == mDatePref) {
+            showDialog(DIALOG_DATEPICKER);
+        } else if (preference == mTimePref) {
+            // The 24-hour mode may have changed, so recreate the dialog
+            removeDialog(DIALOG_TIMEPICKER);
+            showDialog(DIALOG_TIMEPICKER);
+        } else if (preference == mTime24Pref) {
+            set24Hour(((CheckBoxPreference)mTime24Pref).isChecked());
+            updateTimeAndDateDisplay();
+            timeUpdated();
+        } else if (preference == mTimeZone) {
+            Intent intent = new Intent();
+            intent.setClass(this, ZoneList.class);
+            startActivityForResult(intent, 0);
+        }
+        return false;
+    }
+    
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode,
+            Intent data) {
+        updateTimeAndDateDisplay();
+    }
+    
+    private void timeUpdated() {
+        Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED);
+        sendBroadcast(timeChanged);
+    }
+    
+    /*  Get & Set values from the system settings  */
+    
+    private boolean is24Hour() {
+        return DateFormat.is24HourFormat(this);
+    }
+    
+    private void set24Hour(boolean is24Hour) {
+        Settings.System.putString(getContentResolver(),
+                Settings.System.TIME_12_24,
+                is24Hour? HOURS_24 : HOURS_12);
+    }
+    
+    private String getDateFormat() {
+        return Settings.System.getString(getContentResolver(), 
+                Settings.System.DATE_FORMAT);
+    }
+    
+    private boolean getAutoState() {
+        try {
+            return Settings.System.getInt(getContentResolver(), 
+                Settings.System.AUTO_TIME) > 0;            
+        } catch (SettingNotFoundException snfe) {
+            return true;
+        }
+    }
+
+    private void setDateFormat(String format) {
+        Settings.System.putString(getContentResolver(), Settings.System.DATE_FORMAT, format);        
+    }
+    
+    /*  Helper routines to format timezone */
+    
+    private String getTimeZoneText() {
+        TimeZone    tz = java.util.Calendar.getInstance().getTimeZone();
+        boolean daylight = tz.inDaylightTime(new Date());
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(formatOffset(tz.getRawOffset() +
+                               (daylight ? tz.getDSTSavings() : 0))).
+            append(", ").
+            append(tz.getDisplayName(daylight, TimeZone.LONG));
+
+        return sb.toString();        
+    }
+
+    private char[] formatOffset(int off) {
+        off = off / 1000 / 60;
+
+        char[] buf = new char[9];
+        buf[0] = 'G';
+        buf[1] = 'M';
+        buf[2] = 'T';
+
+        if (off < 0) {
+            buf[3] = '-';
+            off = -off;
+        } else {
+            buf[3] = '+';
+        }
+
+        int hours = off / 60; 
+        int minutes = off % 60;
+
+        buf[4] = (char) ('0' + hours / 10);
+        buf[5] = (char) ('0' + hours % 10);
+
+        buf[6] = ':';
+
+        buf[7] = (char) ('0' + minutes / 10);
+        buf[8] = (char) ('0' + minutes % 10);
+
+        return buf;
+    }
+    
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateTimeAndDateDisplay();
+        }
+    };
+}
diff --git a/src/com/android/settings/DateTimeSettingsSetupWizard.java b/src/com/android/settings/DateTimeSettingsSetupWizard.java
new file mode 100644
index 0000000..8dd970b
--- /dev/null
+++ b/src/com/android/settings/DateTimeSettingsSetupWizard.java
@@ -0,0 +1,41 @@
+/*
+ * 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.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.view.View.OnClickListener;
+import android.widget.LinearLayout;
+
+public class DateTimeSettingsSetupWizard extends DateTimeSettings implements OnClickListener {
+    private View mNextButton;
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        requestWindowFeature(Window.FEATURE_NO_TITLE); 
+        super.onCreate(icicle);
+        setContentView(R.layout.date_time_settings_setupwizard);
+        mNextButton = findViewById(R.id.next_button);
+        mNextButton.setOnClickListener(this);
+    }
+
+    public void onClick(View v) {
+        setResult(RESULT_OK);
+        finish();
+    }
+}
diff --git a/src/com/android/settings/DebugIntentSender.java b/src/com/android/settings/DebugIntentSender.java
new file mode 100644
index 0000000..9fed947
--- /dev/null
+++ b/src/com/android/settings/DebugIntentSender.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2006 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.widget.EditText;
+import android.widget.Button;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.text.TextUtils;
+import android.text.Spannable;
+import android.text.Selection;
+import android.net.Uri;
+
+/**
+ * A simple activity that provides a UI for sending intents
+ */
+public class DebugIntentSender extends Activity {
+    private EditText mIntentField;
+    private EditText mDataField;
+    private EditText mAccountField;
+    private EditText mResourceField;
+    private Button mSendBroadcastButton;
+    private Button mStartActivityButton;
+    private View.OnClickListener mClicked = new View.OnClickListener() {
+        public void onClick(View v) {
+            if ((v == mSendBroadcastButton) ||
+                       (v == mStartActivityButton)) {
+                String intentAction = mIntentField.getText().toString();
+                String intentData = mDataField.getText().toString();
+                String account = mAccountField.getText().toString();
+                String resource = mResourceField.getText().toString();
+
+                Intent intent = new Intent(intentAction);
+                if (!TextUtils.isEmpty(intentData)) {
+                    intent.setData(Uri.parse(intentData));
+                }
+                intent.putExtra("account", account);
+                intent.putExtra("resource", resource);
+                if (v == mSendBroadcastButton) {
+                    sendBroadcast(intent);
+                } else {
+                    startActivity(intent);
+                }
+
+                setResult(RESULT_OK);
+                finish();
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.intent_sender);
+
+        mIntentField = (EditText) findViewById(R.id.intent);
+        mIntentField.setText(Intent.ACTION_SYNC);
+        Selection.selectAll((Spannable) mIntentField.getText());
+
+        mDataField = (EditText) findViewById(R.id.data);
+        mDataField.setBackgroundResource(android.R.drawable.editbox_background);
+
+        mAccountField = (EditText) findViewById(R.id.account);
+        mResourceField = (EditText) findViewById(R.id.resource);
+
+        mSendBroadcastButton = (Button) findViewById(R.id.sendbroadcast);
+        mSendBroadcastButton.setOnClickListener(mClicked);
+
+        mStartActivityButton = (Button) findViewById(R.id.startactivity);
+        mStartActivityButton.setOnClickListener(mClicked);
+    }
+}
diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java
new file mode 100644
index 0000000..8eed563
--- /dev/null
+++ b/src/com/android/settings/DefaultRingtonePreference.java
@@ -0,0 +1,62 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.preference.RingtonePreference;
+import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
+
+public class DefaultRingtonePreference extends RingtonePreference {
+    private static final String TAG = "DefaultRingtonePreference";
+    
+    public DefaultRingtonePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
+        super.onPrepareRingtonePickerIntent(ringtonePickerIntent);
+        
+        /*
+         * Since this preference is for choosing the default ringtone, it
+         * doesn't make sense to show a 'Default' item.
+         */
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
+        
+        /*
+         * Similarly, 'Silent' shouldn't be shown here. 
+         */
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
+    }
+
+    @Override
+    protected void onSaveRingtone(Uri ringtoneUri) {
+        RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri);
+    }
+
+    @Override
+    protected Uri onRestoreRingtone() {
+        return RingtoneManager.getActualDefaultRingtoneUri(getContext(), getRingtoneType());
+    }
+    
+}
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
new file mode 100644
index 0000000..155f085
--- /dev/null
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -0,0 +1,88 @@
+/*
+ * 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.os.BatteryManager;
+import android.os.Bundle;
+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;
+
+/*
+ * Displays preferences for application developers.
+ */
+public class DevelopmentSettings extends PreferenceActivity {
+
+    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) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.development_prefs);
+
+        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.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
+    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 == mEnableAdb) {
+            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() ? 
+                    (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
new file mode 100644
index 0000000..be01f7d
--- /dev/null
+++ b/src/com/android/settings/DeviceInfoSettings.java
@@ -0,0 +1,153 @@
+/*
+ * 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.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DeviceInfoSettings extends PreferenceActivity {
+
+    private static final String TAG = "DeviceInfoSettings";
+    private static final boolean LOGD = false || Config.LOGD;
+
+    private static final String KEY_CONTAINER = "container";
+    private static final String KEY_TEAM = "team";
+    private static final String KEY_CONTRIBUTORS = "contributors";
+    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) {
+        super.onCreate(icicle);
+        
+        addPreferencesFromResource(R.xml.device_info_settings);
+       
+        setStringSummary("firmware_version", Build.VERSION.RELEASE);
+        setValueSummary("baseband_version", "gsm.version.baseband");
+        setStringSummary("device_model", Build.MODEL);
+        setStringSummary("build_number", Build.DISPLAY);
+        findPreference("kernel_version").setSummary(getFormattedKernelVersion());
+
+        /*
+         * Settings is a generic app and should not contain any device-specific
+         * info.
+         */
+        
+        // These are contained in the "container" preference group
+        PreferenceGroup parentPreference = (PreferenceGroup) findPreference(KEY_CONTAINER);
+        Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_TERMS,
+                Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
+        Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_LICENSE,
+                Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
+        Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_COPYRIGHT,
+                Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
+        Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_TEAM,
+                Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
+        
+        // These are contained by the root preference screen
+        parentPreference = getPreferenceScreen();
+        Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference,
+                KEY_SYSTEM_UPDATE_SETTINGS,
+                Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
+        Utils.updatePreferenceToSpecificActivityOrRemove(this, parentPreference, KEY_CONTRIBUTORS,
+                Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
+    }
+    
+    private void setStringSummary(String preference, String value) {
+        try {
+            findPreference(preference).setSummary(value);
+        } catch (RuntimeException e) {
+            findPreference(preference).setSummary(
+                getResources().getString(R.string.device_info_default));
+        }
+    }
+    
+    private void setValueSummary(String preference, String property) {
+        try {
+            findPreference(preference).setSummary(
+                    SystemProperties.get(property, 
+                            getResources().getString(R.string.device_info_default)));
+        } catch (RuntimeException e) {
+
+        }
+    }
+
+    private String getFormattedKernelVersion() {
+        String procVersionStr;
+
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader("/proc/version"), 256);
+            try {
+                procVersionStr = reader.readLine();
+            } finally {
+                reader.close();
+            }
+
+            final String PROC_VERSION_REGEX =
+                "\\w+\\s+" + /* ignore: Linux */
+                "\\w+\\s+" + /* ignore: version */
+                "([^\\s]+)\\s+" + /* group 1: 2.6.22-omap1 */
+                "\\(([^\\s@]+(?:@[^\\s.]+)?)[^)]*\\)\\s+" + /* group 2: (xxxxxx@xxxxx.constant) */
+                "\\([^)]+\\)\\s+" + /* ignore: (gcc ..) */
+                "([^\\s]+)\\s+" + /* group 3: #26 */
+                "(?:PREEMPT\\s+)?" + /* ignore: PREEMPT (optional) */
+                "(.+)"; /* group 4: date */
+
+            Pattern p = Pattern.compile(PROC_VERSION_REGEX);
+            Matcher m = p.matcher(procVersionStr);
+
+            if (!m.matches()) {
+                Log.e(TAG, "Regex did not match on /proc/version: " + procVersionStr);
+                return "Unavailable";
+            } else if (m.groupCount() < 4) {
+                Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
+                        + " groups");
+                return "Unavailable";
+            } else {
+                return (new StringBuilder(m.group(1)).append("\n").append(
+                        m.group(2)).append(" ").append(m.group(3)).append("\n")
+                        .append(m.group(4))).toString();
+            }
+        } catch (IOException e) {  
+            Log.e(TAG,
+                "IO Exception when getting kernel version for Device Info screen",
+                e);
+
+            return "Unavailable";
+        }
+    }
+
+}
diff --git a/src/com/android/settings/Display.java b/src/com/android/settings/Display.java
new file mode 100644
index 0000000..f90e0f0
--- /dev/null
+++ b/src/com/android/settings/Display.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2006 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.app.ActivityManagerNative;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+
+public class Display extends Activity implements View.OnClickListener {
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.display);
+
+        mFontSize = (Spinner) findViewById(R.id.fontSize);
+        mFontSize.setOnItemSelectedListener(mFontSizeChanged);
+        String[] states = new String[3];
+        Resources r = getResources();
+        states[0] = r.getString(R.string.small_font);
+        states[1] = r.getString(R.string.medium_font);
+        states[2] = r.getString(R.string.large_font);
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+                android.R.layout.simple_spinner_item, states);
+        adapter.setDropDownViewResource(
+                android.R.layout.simple_spinner_dropdown_item);
+        mFontSize.setAdapter(adapter);
+
+        mPreview = (TextView) findViewById(R.id.preview);
+        mPreview.setText(r.getText(R.string.font_size_preview_text));
+
+        Button save = (Button) findViewById(R.id.save);
+        save.setText(r.getText(R.string.font_size_save));
+        save.setOnClickListener(this);
+
+        mTextSizeTyped = new TypedValue();
+        TypedArray styledAttributes = 
+            obtainStyledAttributes(android.R.styleable.TextView);
+        styledAttributes.getValue(android.R.styleable.TextView_textSize,
+                mTextSizeTyped);
+
+        DisplayMetrics metrics = getResources().getDisplayMetrics();
+        mDisplayMetrics = new DisplayMetrics();
+        mDisplayMetrics.density = metrics.density;
+        mDisplayMetrics.heightPixels = metrics.heightPixels;
+        mDisplayMetrics.scaledDensity = metrics.scaledDensity;
+        mDisplayMetrics.widthPixels = metrics.widthPixels;
+        mDisplayMetrics.xdpi = metrics.xdpi;
+        mDisplayMetrics.ydpi = metrics.ydpi;
+        
+        styledAttributes.recycle();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        try {
+            mCurConfig.updateFrom(
+                ActivityManagerNative.getDefault().getConfiguration());
+        } catch (RemoteException e) {
+        }
+        if (mCurConfig.fontScale < 1) {
+            mFontSize.setSelection(0);
+        } else if (mCurConfig.fontScale > 1) {
+            mFontSize.setSelection(2);
+        } else {
+            mFontSize.setSelection(1);
+        }
+        updateFontScale();
+    }
+
+    private void updateFontScale() {
+        mDisplayMetrics.scaledDensity = mDisplayMetrics.density *
+                mCurConfig.fontScale;
+
+        float size = mTextSizeTyped.getDimension(mDisplayMetrics);
+        mPreview.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
+    }
+
+    public void onClick(View v) {
+        try {
+            ActivityManagerNative.getDefault().updateConfiguration(mCurConfig);
+        } catch (RemoteException e) {
+        }
+        finish();
+    }
+
+    private Spinner.OnItemSelectedListener mFontSizeChanged
+                                    = new Spinner.OnItemSelectedListener() {
+        public void onItemSelected(android.widget.AdapterView av, View v,
+                                    int position, long id) {
+            if (position == 0) {
+                mCurConfig.fontScale = .75f;
+            } else if (position == 2) {
+                mCurConfig.fontScale = 1.25f;
+            } else {
+                mCurConfig.fontScale = 1.0f;
+            }
+
+            updateFontScale();
+        }
+
+        public void onNothingSelected(android.widget.AdapterView av) {
+        }
+    };
+
+    private Spinner mFontSize;
+    private TextView mPreview;
+    private TypedValue mTextSizeTyped;
+    private DisplayMetrics mDisplayMetrics;
+    private Configuration mCurConfig = new Configuration();
+}
diff --git a/src/com/android/settings/EditPinPreference.java b/src/com/android/settings/EditPinPreference.java
new file mode 100644
index 0000000..ee3143c
--- /dev/null
+++ b/src/com/android/settings/EditPinPreference.java
@@ -0,0 +1,84 @@
+/*
+ * 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.preference.EditTextPreference;
+import android.text.method.DigitsKeyListener;
+import android.text.method.PasswordTransformationMethod;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+
+import java.util.Map;
+
+/**
+ * TODO: Add a soft dialpad for PIN entry.
+ */
+class EditPinPreference extends EditTextPreference {
+
+    private boolean mDialogOpen;
+    
+    interface OnPinEnteredListener {
+        void onPinEntered(EditPinPreference preference, boolean positiveResult);
+    }
+    
+    private OnPinEnteredListener mPinListener;
+    
+    public EditPinPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public EditPinPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+    
+    public void setOnPinEnteredListener(OnPinEnteredListener listener) {
+        mPinListener = listener;
+    }
+    
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+        
+        final EditText editText = (EditText) view.findViewById(android.R.id.edit);
+        
+        if (editText != null) {
+            editText.setSingleLine(true);
+            editText.setTransformationMethod(PasswordTransformationMethod.getInstance());
+            editText.setKeyListener(DigitsKeyListener.getInstance());
+        }
+    }
+
+    public boolean isDialogOpen() {
+        return mDialogOpen;
+    }
+    
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        mDialogOpen = false;
+        if (mPinListener != null) {
+            mPinListener.onPinEntered(this, positiveResult);
+        }
+    }
+    
+    public void showPinDialog() {
+        mDialogOpen = true;
+        showDialog(null);
+    }
+}
diff --git a/src/com/android/settings/GadgetPickActivity.java b/src/com/android/settings/GadgetPickActivity.java
new file mode 100644
index 0000000..840a6a5
--- /dev/null
+++ b/src/com/android/settings/GadgetPickActivity.java
@@ -0,0 +1,131 @@
+/*
+ * 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.LauncherActivity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.gadget.GadgetProviderInfo;
+import android.gadget.GadgetManager;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ListView;
+import android.util.Log;
+
+import java.text.Collator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+public class GadgetPickActivity extends LauncherActivity
+{
+    private static final String TAG = "GadgetPickActivity";
+
+    GadgetManager mGadgetManager;
+    int mGadgetId;
+    
+    public GadgetPickActivity() {
+        mGadgetManager = GadgetManager.getInstance(this);
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        Bundle extras = getIntent().getExtras();
+        mGadgetId = extras.getInt(GadgetManager.EXTRA_GADGET_ID);
+
+        setResultData(RESULT_CANCELED);
+    }
+
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id)
+    {
+        Intent intent = intentForPosition(position);
+        int result;
+        try {
+            mGadgetManager.bindGadgetId(mGadgetId, intent.getComponent());
+            result = RESULT_OK;
+        } catch (IllegalArgumentException e) {
+            // This is thrown if they're already bound, or otherwise somehow
+            // bogus.  Set the result to canceled, and exit.  The app *should*
+            // clean up at this point.  We could pass the error along, but
+            // it's not clear that that's useful -- the gadget will simply not
+            // appear.
+            result = RESULT_CANCELED;
+        }
+        setResultData(result);
+        finish();
+    }
+    
+    @Override
+    public List<ListItem> makeListItems() {
+        List<GadgetProviderInfo> installed = mGadgetManager.getInstalledProviders();
+        PackageManager pm = getPackageManager();
+
+        Drawable defaultIcon = null;
+        IconResizer resizer = new IconResizer();
+
+        ArrayList<ListItem> result = new ArrayList();
+        final int N = installed.size();
+        for (int i=0; i<N; i++) {
+            GadgetProviderInfo info = installed.get(i);
+
+            LauncherActivity.ListItem item = new LauncherActivity.ListItem();
+            item.packageName = info.provider.getPackageName();
+            item.className = info.provider.getClassName();
+            
+            item.label = info.label;
+            if (info.icon != 0) {
+                Drawable d = pm.getDrawable( item.packageName, info.icon, null);
+                if (d != null) {
+                    item.icon = resizer.createIconThumbnail(d);
+                } else {
+                    Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
+                            + " for package: " + item.packageName);
+                }
+            }
+            if (item.icon == null) {
+                // (including error case above)
+                if (defaultIcon == null) {
+                    // TODO: Load standard icon.
+                }
+                item.icon = defaultIcon;
+            }
+            
+            result.add(item);
+        }
+
+        Collections.sort(result, new Comparator<ListItem>() {
+                Collator mCollator = Collator.getInstance();
+                public int compare(ListItem lhs, ListItem rhs) {
+                    return mCollator.compare(lhs.label, rhs.label);
+                }
+            });
+        return result;
+    }
+
+    void setResultData(int code) {
+        Intent result = new Intent();
+        result.putExtra(GadgetManager.EXTRA_GADGET_ID, mGadgetId);
+        setResult(code, result);
+    }
+}
+
diff --git a/src/com/android/settings/InputMethodsSettings.java b/src/com/android/settings/InputMethodsSettings.java
new file mode 100644
index 0000000..51b770d
--- /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());
+                prefScreen.setTitle(getResources().getString(
+                        R.string.input_methods_settings_label_format, label));
+                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
new file mode 100644
index 0000000..327874b
--- /dev/null
+++ b/src/com/android/settings/InstalledAppDetails.java
@@ -0,0 +1,478 @@
+
+
+/**
+ * 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 com.android.settings.R;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageStats;
+import android.content.pm.PackageManager.NameNotFoundException;
+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 java.util.ArrayList;
+import java.util.List;
+import android.content.ComponentName;
+import android.view.View;
+import android.widget.AppSecurityPermissions;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * 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;
+    private ApplicationInfo mAppInfo;
+    private Button mAppButton;
+    private Button mActivitiesButton;
+    private boolean mCanUninstall;
+    private boolean localLOGV=Config.LOGV || false;
+    private TextView mAppSnippetSize;
+    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;
+    private Button mForceStopButton;
+    
+    PackageStats mSizeInfo;
+    private Button mManageSpaceButton;
+    private PackageManager mPm;
+    
+    //internal constants used in Handler
+    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) {
+                case CLEAR_USER_DATA:
+                    processClearMsg(msg);
+                    break;
+                case GET_PKG_SIZE:
+                    refreshSizeInfo(msg);
+                    break;
+                case CLEAR_CACHE:
+                    // Refresh size info
+                    mPm.getPackageSizeInfo(mAppInfo.packageName, mSizeObserver);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+    
+    private boolean isUninstallable() {
+        if (((mAppInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) && 
+                ((mAppInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0)) {
+            return false;
+        }
+        return true;
+    }
+    
+    class ClearUserDataObserver extends IPackageDataObserver.Stub {
+       public void onRemoveCompleted(final String packageName, final boolean succeeded) {
+           final Message msg = mHandler.obtainMessage(CLEAR_USER_DATA);
+           msg.arg1 = succeeded?OP_SUCCESSFUL:OP_FAILED;
+           mHandler.sendMessage(msg);
+        }
+    }
+    
+    class PkgSizeObserver extends IPackageStatsObserver.Stub {
+        public int idx;
+        public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) {
+             Message msg = mHandler.obtainMessage(GET_PKG_SIZE);
+             Bundle data = new Bundle();
+             data.putParcelable(ATTR_PACKAGE_STATS, pStats);
+             msg.setData(data);
+             mHandler.sendMessage(msg);
+            
+         }
+     }
+    
+    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) {
+        if (size == SIZE_INVALID) {
+            return mInvalidSizeStr.toString();
+        }
+        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;
+            }
+        } else {
+            visible = true;
+            mAppButtonState = AppButtonStates.UNINSTALL;
+            mAppButtonText = getText(R.string.uninstall_text);
+        }
+        if(visible) {
+            mAppButton.setText(mAppButtonText);
+            mAppButton.setVisibility(View.VISIBLE);
+        } else {
+            mAppButton.setVisibility(View.GONE);
+        }
+    }
+    
+    /** Called when the activity is first created. */
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        //get package manager
+        mPm = getPackageManager();
+        //get application's name from intent
+        Intent intent = getIntent();
+        final String packageName = intent.getStringExtra(ManageApplications.APP_PKG_NAME);
+        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, 
+                    PackageManager.GET_UNINSTALLED_PACKAGES);
+        } catch (NameNotFoundException e) {
+            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(mAppInfo.loadIcon(mPm));
+        //set application name TODO version
+        CharSequence appName = mAppInfo.loadLabel(mPm);
+        if(appName == null) {
+            appName = getString(_UNKNOWN_APP);
+        }
+        ((TextView)findViewById(R.id.app_name)).setText(appName);
+        mAppSnippetSize = ((TextView)findViewById(R.id.app_size));
+        mAppSnippetSize.setText(totalSizeStr);
+        //TODO download str and download url
+        //set values on views
+        mTotalSize = (TextView)findViewById(R.id.total_size_text);
+        mTotalSize.setText(totalSizeStr);
+        mAppSize = (TextView)findViewById(R.id.application_size_text);
+        mAppSize.setText(appSizeStr);
+        mDataSize = (TextView)findViewById(R.id.data_size_text);
+        mDataSize.setText(dataSizeStr);
+         
+         mAppButton = ((Button)findViewById(R.id.uninstall_button));
+        //determine if app is a system app
+         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);
+         mCacheSize.setText(mComputingStr);
+         mClearCacheButton = (Button) findViewById(R.id.clear_cache_button);
+         mForceStopButton = (Button) findViewById(R.id.force_stop_button);
+         mForceStopButton.setOnClickListener(this);
+         
+         //clear activities
+         mActivitiesButton = (Button)findViewById(R.id.clear_activities_button);
+         List<ComponentName> prefActList = new ArrayList<ComponentName>();
+         //intent list cannot be null. so pass empty list
+         List<IntentFilter> intentList = new ArrayList<IntentFilter>();
+         mPm.getPreferredActivities(intentList,  prefActList, packageName);
+         if(localLOGV) Log.i(TAG, "Have "+prefActList.size()+" number of activities in prefered list");
+         TextView autoLaunchView = (TextView)findViewById(R.id.auto_launch);
+         if(prefActList.size() <= 0) {
+             //disable clear activities button
+             autoLaunchView.setText(R.string.auto_launch_disable_text);
+             mActivitiesButton.setEnabled(false);
+         } else {
+             autoLaunchView.setText(R.string.auto_launch_enable_text);
+             mActivitiesButton.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);
+         }
+    }
+    
+    private void displayErrorDialog(int msgId, final boolean finish, final boolean changed) {
+        //display confirmation dialog
+        new AlertDialog.Builder(this)
+        .setTitle(getString(R.string.app_not_found_dlg_title))
+        .setIcon(android.R.drawable.ic_dialog_alert)
+        .setMessage(getString(msgId))
+        .setNeutralButton(getString(R.string.dlg_ok), 
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        //force to recompute changed value
+                        setIntentAndFinish(finish, changed);
+                    }
+                }
+        )
+        .show();
+    }
+    
+    private void setIntentAndFinish(boolean finish, boolean appChanged) {
+        if(localLOGV) Log.i(TAG, "appChanged="+appChanged);
+        Intent intent = new Intent();
+        intent.putExtra(ManageApplications.APP_CHG, appChanged);
+        setResult(ManageApplications.RESULT_OK, intent);
+        mAppButton.setEnabled(false);
+        if(finish) {
+            finish();
+        }
+    }
+    
+    /*
+     * Private method to handle get size info notification from observer when
+     * the async operation from PackageManager is complete. The current user data
+     * info has to be refreshed in the manage applications screen as well as the current screen.
+     */
+    private void refreshSizeInfo(Message msg) {
+        boolean changed = false;
+        PackageStats newPs = msg.getData().getParcelable(ATTR_PACKAGE_STATS);
+        long newTot = newPs.cacheSize+newPs.codeSize+newPs.dataSize;
+        if(mSizeInfo == null) {
+            mSizeInfo = newPs;
+            String str = getSizeStr(newTot);
+            mTotalSize.setText(str);
+            mAppSnippetSize.setText(str);
+            mAppSize.setText(getSizeStr(newPs.codeSize));
+            mDataSize.setText(getSizeStr(newPs.dataSize));
+            mCacheSize.setText(getSizeStr(newPs.cacheSize));
+        } else {
+            long oldTot = mSizeInfo.cacheSize+mSizeInfo.codeSize+mSizeInfo.dataSize;
+            if(newTot != oldTot) {
+                String str = getSizeStr(newTot);
+                mTotalSize.setText(str);
+                mAppSnippetSize.setText(str);
+                changed = true;
+            }
+            if(newPs.codeSize != mSizeInfo.codeSize) {
+                mAppSize.setText(getSizeStr(newPs.codeSize));
+                changed = true;
+            }
+            if(newPs.dataSize != mSizeInfo.dataSize) {
+                mDataSize.setText(getSizeStr(newPs.dataSize));
+                changed = true;
+            }
+            if(newPs.cacheSize != mSizeInfo.cacheSize) {
+                mCacheSize.setText(getSizeStr(newPs.cacheSize));
+                changed = true;
+            }
+            if(changed) {
+                mSizeInfo = newPs;
+            }
+        }
+        
+        long data = mSizeInfo.dataSize;
+        // 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);
+        }
+    }
+    
+    /*
+     * Private method to handle clear message notification from observer when
+     * the async operation from PackageManager is complete
+     */
+    private void processClearMsg(Message msg) {
+        int result = msg.arg1;
+        String packageName = mAppInfo.packageName;
+        if(result == OP_SUCCESSFUL) {
+            Log.i(TAG, "Cleared user data for system package:"+packageName);
+            mPm.getPackageSizeInfo(packageName, mSizeObserver);
+        } else {
+            mAppButton.setText(R.string.clear_user_data_text);
+            mAppButton.setEnabled(true);
+        }
+    }
+    
+    /*
+     * Private method to initiate clearing user data when the user clicks the clear data 
+     * button for a system package
+     */
+    private  void initiateClearUserDataForSysPkg() {
+        mAppButton.setEnabled(false);
+        //invoke uninstall or clear user data based on sysPackage
+        String packageName = mAppInfo.packageName;
+        Log.i(TAG, "Clearing user data for system package");
+        if(mClearDataObserver == null) {
+            mClearDataObserver = new ClearUserDataObserver();
+        }
+        ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+        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 {
+                mAppButton.setText(R.string.recompute_size);
+        }
+    }
+    
+    /*
+     * Method implementing functionality of buttons clicked
+     * @see android.view.View.OnClickListener#onClick(android.view.View)
+     */
+    public void onClick(View v) {
+        String packageName = mAppInfo.packageName;
+        if(v == mAppButton) {
+            if(mCanUninstall) {
+                //display confirmation dialog
+                new AlertDialog.Builder(this)
+                .setTitle(getString(R.string.clear_data_dlg_title))
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setMessage(getString(R.string.clear_data_dlg_text))
+                .setPositiveButton(R.string.dlg_ok, this)
+                .setNegativeButton(R.string.dlg_cancel, this)
+                .show();
+            } else {
+                //create new intent to launch Uninstaller activity
+                Uri packageURI = Uri.parse("package:"+packageName);
+                Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
+                startActivity(uninstallIntent);
+                setIntentAndFinish(true, true);
+            }
+        } else if(v == mActivitiesButton) {
+            mPm.clearPackagePreferredActivities(packageName);
+            mActivitiesButton.setEnabled(false);
+        } else if(v == mManageSpaceButton) {
+            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);
+        } else if (v == mForceStopButton) {
+            ActivityManager am = (ActivityManager)getSystemService(
+                    Context.ACTIVITY_SERVICE);
+            am.restartPackage(packageName);
+        }
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        if(which == AlertDialog.BUTTON_POSITIVE) {
+            //invoke uninstall or clear user data based on sysPackage
+            initiateClearUserDataForSysPkg();
+        } else {
+            //cancel do nothing just retain existing screen
+        }
+    }
+}
+
diff --git a/src/com/android/settings/LanguageSettings.java b/src/com/android/settings/LanguageSettings.java
new file mode 100644
index 0000000..8463d26
--- /dev/null
+++ b/src/com/android/settings/LanguageSettings.java
@@ -0,0 +1,247 @@
+/*
+ * 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.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+import java.util.HashSet;
+import java.util.List;
+
+public class LanguageSettings extends PreferenceActivity {
+    
+    private final String[] mSettingsUiKey = {
+            "auto_caps",
+            "auto_replace",
+            "auto_punctuate",
+    };
+    
+    // Note: Order of this array should correspond to the order of the above array
+    private final String[] mSettingsSystemId = {
+            System.TEXT_AUTO_CAPS,
+            System.TEXT_AUTO_REPLACE,
+            System.TEXT_AUTO_PUNCTUATE,
+    };
+
+    // Note: Order of this array should correspond to the order of the above array
+    private final int[] mSettingsDefault = {
+            1,
+            1,
+            1,
+    };
+    
+    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.language_settings);
+
+        if (getAssets().getLocales().length == 1) {
+            getPreferenceScreen().
+                removePreference(findPreference("language_category"));
+        }
+    
+        Configuration config = getResources().getConfiguration();
+        if (config.keyboard != Configuration.KEYBOARD_QWERTY) {
+            getPreferenceScreen().removePreference(
+                    getPreferenceScreen().findPreference("hardkeyboard_category"));
+        } else {
+            ContentResolver resolver = getContentResolver();
+            for (int i = 0; i < mSettingsUiKey.length; i++) {
+                CheckBoxPreference pref = (CheckBoxPreference) findPreference(mSettingsUiKey[i]);
+                pref.setChecked(System.getInt(resolver, mSettingsSystemId[i],
+                                              mSettingsDefault[i]) > 0);
+            }
+        }
+
+        onCreateIMM();
+    }
+    
+    private void onCreateIMM() {
+        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+
+        mInputMethodProperties = imm.getInputMethodList();
+
+        mLastInputMethodId = Settings.Secure.getString(getContentResolver(),
+            Settings.Secure.DEFAULT_INPUT_METHOD);
+        
+        PreferenceGroup textCategory = (PreferenceGroup) findPreference("text_category");
+        
+        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);
+            textCategory.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());
+                CharSequence settingsLabel = getResources().getString(
+                        R.string.input_methods_settings_label_format, label);
+                prefScreen.setTitle(settingsLabel);
+                textCategory.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) {
+
+        // Physical keyboard stuff
+        for (int i = 0; i < mSettingsUiKey.length; i++) {
+            if (mSettingsUiKey[i].equals(preference.getKey())) {
+                System.putInt(getContentResolver(), mSettingsSystemId[i], 
+                        ((CheckBoxPreference)preference).isChecked()? 1 : 0);
+                return true;
+            }
+        }
+
+        // Input Method stuff
+        // 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 super.onPreferenceTreeClick(preferenceScreen, preference);
+    }
+
+}
diff --git a/src/com/android/settings/LauncherGadgetBinder.java b/src/com/android/settings/LauncherGadgetBinder.java
new file mode 100644
index 0000000..f7b5a61
--- /dev/null
+++ b/src/com/android/settings/LauncherGadgetBinder.java
@@ -0,0 +1,225 @@
+/*
+ * 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.Activity;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.gadget.GadgetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class LauncherGadgetBinder extends Activity {
+    private static final String TAG = "LauncherGadgetBinder";
+    private static final boolean LOGD = true;
+    
+    static final String AUTHORITY = "com.android.launcher.settings";
+    static final String TABLE_FAVORITES = "favorites";
+    
+    static final String EXTRA_BIND_SOURCES = "com.android.launcher.settings.bindsources";
+    static final String EXTRA_BIND_TARGETS = "com.android.launcher.settings.bindtargets";
+
+    static final String EXTRA_GADGET_BITMAPS = "com.android.camera.gadgetbitmaps";
+    
+    /**
+     * {@link ContentProvider} constants pulled over from Launcher
+     */
+    static final class LauncherProvider implements BaseColumns {
+        static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + TABLE_FAVORITES);
+
+        static final String ITEM_TYPE = "itemType";
+        static final String GADGET_ID = "gadgetId";
+        static final String ICON = "icon";
+
+        static final int ITEM_TYPE_GADGET = 4;
+        static final int ITEM_TYPE_WIDGET_CLOCK = 1000;
+        static final int ITEM_TYPE_WIDGET_SEARCH = 1001;
+        static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002;
+    }
+    
+    static final String[] BIND_PROJECTION = new String[] {
+        LauncherProvider._ID,
+        LauncherProvider.ITEM_TYPE,
+        LauncherProvider.GADGET_ID,
+        LauncherProvider.ICON,
+    };
+    
+    static final int INDEX_ID = 0;
+    static final int INDEX_ITEM_TYPE = 1;
+    static final int INDEX_GADGET_ID = 2;
+    static final int INDEX_ICON = 3;
+    
+    static final ComponentName BIND_PHOTO_GADGET = new ComponentName("com.android.camera",
+            "com.android.camera.PhotoGadgetBind");
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        finish();
+
+        // This helper reaches into the Launcher database and binds any unlinked
+        // gadgets. If will remove any items that can't be bound successfully.
+        // We protect this binder at the manifest level by asserting the caller
+        // has the Launcher WRITE_SETTINGS permission.
+        
+        final Intent intent = getIntent();
+        final Bundle extras = intent.getExtras();
+        
+        int[] bindSources = null;
+        ArrayList<ComponentName> bindTargets = null;
+        Exception exception = null;
+
+        try {
+            bindSources = extras.getIntArray(EXTRA_BIND_SOURCES);
+            bindTargets = intent.getParcelableArrayListExtra(EXTRA_BIND_TARGETS);
+        } catch (ClassCastException ex) {
+            exception = ex;
+        }
+        
+        if (exception != null || bindSources == null || bindTargets == null ||
+                bindSources.length != bindTargets.size()) {
+            Log.w(TAG, "Problem reading incoming bind request, or invalid request", exception);
+            return;
+        }
+        
+        final String selectWhere = buildOrWhereString(LauncherProvider.ITEM_TYPE, bindSources);
+        
+        final ContentResolver resolver = getContentResolver();
+        final GadgetManager gadgetManager = GadgetManager.getInstance(this);
+        
+        boolean foundPhotoGadgets = false;
+        final ArrayList<Integer> photoGadgetIds = new ArrayList<Integer>();
+        final ArrayList<Bitmap> photoBitmaps = new ArrayList<Bitmap>();
+        
+        Cursor c = null;
+        
+        try {
+            c = resolver.query(LauncherProvider.CONTENT_URI,
+                    BIND_PROJECTION, selectWhere, null, null);
+            
+            if (LOGD) Log.d(TAG, "found bind cursor count="+c.getCount());
+            
+            final ContentValues values = new ContentValues();
+            while (c != null && c.moveToNext()) {
+                long favoriteId = c.getLong(INDEX_ID);
+                int itemType = c.getInt(INDEX_ITEM_TYPE);
+                int gadgetId = c.getInt(INDEX_GADGET_ID);
+                byte[] iconData = c.getBlob(INDEX_ICON);
+                
+                // Find the binding target for this type
+                ComponentName targetGadget = null;
+                for (int i = 0; i < bindSources.length; i++) {
+                    if (bindSources[i] == itemType) {
+                        targetGadget = bindTargets.get(i);
+                        break;
+                    }
+                }
+                
+                if (LOGD) Log.d(TAG, "found matching targetGadget="+targetGadget.toString()+" for favoriteId="+favoriteId);
+                
+                boolean bindSuccess = false;
+                try {
+                    gadgetManager.bindGadgetId(gadgetId, targetGadget);
+                    bindSuccess = true;
+                } catch (RuntimeException ex) {
+                    Log.w(TAG, "Problem binding gadget", ex);
+                }
+                
+                // Handle special case of photo gadget by loading bitmap and
+                // preparing for later binding
+                if (bindSuccess && iconData != null &&
+                        itemType == LauncherProvider.ITEM_TYPE_WIDGET_PHOTO_FRAME) {
+                    Bitmap bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.length);
+                    
+                    photoGadgetIds.add(gadgetId);
+                    photoBitmaps.add(bitmap);
+                    foundPhotoGadgets = true;
+                }
+
+                if (LOGD) Log.d(TAG, "after finished, success="+bindSuccess);
+
+                // Depending on success, update launcher or remove item
+                Uri favoritesUri = ContentUris.withAppendedId(LauncherProvider.CONTENT_URI, favoriteId);
+                if (bindSuccess) {
+                    values.clear();
+                    values.put(LauncherProvider.ITEM_TYPE, LauncherProvider.ITEM_TYPE_GADGET);
+                    values.putNull(LauncherProvider.ICON);
+                    resolver.update(favoritesUri, values, null, null);
+                } else {
+                    resolver.delete(favoritesUri, null, null);
+                }
+                    
+            }
+        } catch (SQLException ex) {
+            Log.w(TAG, "Problem while binding gadgetIds for Launcher", ex);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        
+        if (foundPhotoGadgets) {
+            // Convert gadgetIds into int[]
+            final int N = photoGadgetIds.size();
+            final int[] photoGadgetIdsArray = new int[N];
+            for (int i = 0; i < N; i++) {
+                photoGadgetIdsArray[i] = photoGadgetIds.get(i);
+            }
+            
+            // Launch intent over to handle bitmap binding, but we don't need to
+            // wait around for the result.
+            final Intent bindIntent = new Intent();
+            bindIntent.setComponent(BIND_PHOTO_GADGET);
+            
+            final Bundle bindExtras = new Bundle();
+            bindExtras.putIntArray(GadgetManager.EXTRA_GADGET_IDS, photoGadgetIdsArray);
+            bindExtras.putParcelableArrayList(EXTRA_GADGET_BITMAPS, photoBitmaps);
+            bindIntent.putExtras(bindExtras);
+            
+            startActivity(bindIntent);
+        }
+        
+        if (LOGD) Log.d(TAG, "completely finished with binding for Launcher");
+    }
+    
+    /**
+     * Build a query string that will match any row where the column matches
+     * anything in the values list.
+     */
+    static String buildOrWhereString(String column, int[] values) {
+        StringBuilder selectWhere = new StringBuilder();
+        for (int i = values.length - 1; i >= 0; i--) {
+            selectWhere.append(column).append("=").append(values[i]);
+            if (i > 0) {
+                selectWhere.append(" OR ");
+            }
+        }
+        return selectWhere.toString();
+    }
+    
+}
diff --git a/src/com/android/settings/LocalePicker.java b/src/com/android/settings/LocalePicker.java
new file mode 100644
index 0000000..386d7e0
--- /dev/null
+++ b/src/com/android/settings/LocalePicker.java
@@ -0,0 +1,153 @@
+/*
+ * 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.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.ListActivity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.util.Arrays;
+import java.util.Locale;
+
+public class LocalePicker extends ListActivity {
+    private static final String TAG = "LocalePicker";
+
+    Loc[] mLocales;
+
+    private static class Loc {
+        String label;
+        Locale locale;
+
+        public Loc(String label, Locale locale) {
+            this.label = label;
+            this.locale = locale;
+        }
+
+        @Override
+        public String toString() {
+            return this.label;
+        }
+    }
+
+    int getContentView() {
+        return R.layout.locale_picker;
+    }
+    
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(getContentView());
+
+        String[] locales = getAssets().getLocales();
+        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 == 2) {
+                Locale l = new Locale(s);
+                preprocess[finalSize++] = new Loc(toTitleCase(l.getDisplayLanguage()), l);
+            } else if (len == 5) {
+                String language = s.substring(0, 2);
+                String country = s.substring(3, 5);
+                Locale l = new Locale(language, country);
+
+                if (finalSize == 0) {
+                    preprocess[finalSize++] = new Loc(toTitleCase(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 = toTitleCase(l.getDisplayLanguage());
+                        } else {
+                            preprocess[finalSize-1].label = toTitleCase(preprocess[finalSize-1].locale.getDisplayName());
+                            preprocess[finalSize++] = new Loc(toTitleCase(l.getDisplayName()), l);
+                        }
+                    } else {
+                        String displayName;
+                        if (s.equals("zz_ZZ")) {
+                            displayName = "Pseudo...";
+                        } else {
+                            displayName = toTitleCase(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);
+        getListView().setAdapter(adapter);
+    }
+
+    private static String toTitleCase(String s) {
+        if (s.length() == 0) {
+            return s;
+        }
+
+        return Character.toUpperCase(s.charAt(0)) + s.substring(1);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getListView().requestFocus();
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        try {
+            IActivityManager am = ActivityManagerNative.getDefault();
+            Configuration config = am.getConfiguration();
+
+            Loc loc = mLocales[position];
+            config.locale = loc.locale;
+
+            // indicate this isn't some passing default - the user wants this remembered
+            config.userSetLocale = true;
+
+            am.updateConfiguration(config);
+        } catch (RemoteException e) {
+            // Intentionally left blank
+        }
+        finish();
+    }
+}
diff --git a/src/com/android/settings/LocalePickerInSetupWizard.java b/src/com/android/settings/LocalePickerInSetupWizard.java
new file mode 100644
index 0000000..b160e89
--- /dev/null
+++ b/src/com/android/settings/LocalePickerInSetupWizard.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.ListActivity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.util.Arrays;
+import java.util.Locale;
+
+public class LocalePickerInSetupWizard extends LocalePicker {
+    
+    @Override 
+    int getContentView() {
+        return R.layout.locale_picker_in_setupwizard;
+    }
+
+}
diff --git a/src/com/android/settings/ManageApplications.java b/src/com/android/settings/ManageApplications.java
new file mode 100644
index 0000000..74957ed
--- /dev/null
+++ b/src/com/android/settings/ManageApplications.java
@@ -0,0 +1,1307 @@
+/*
+ * Copyright (C) 2006 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 com.android.settings.R;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+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;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageStats;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
+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.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 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 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.
+ */
+public class ManageApplications extends ListActivity implements
+        OnItemClickListener, DialogInterface.OnCancelListener,
+        DialogInterface.OnClickListener {
+    // TAG for this activity
+    private static final String TAG = "ManageApplications";
+    
+    // Log information boolean
+    private boolean localLOGV = Config.LOGV || false;
+    
+    // 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_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;
+    public static final int FILTER_OPTIONS = MENU_OPTIONS_BASE + 5;
+    // Alert Dialog presented to user to find out the filter option
+    AlertDialog mAlertDlg;
+    // 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 INIT_PKG_INFO = 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;
+    private static final int NEXT_LOAD_STEP = HANDLER_MESSAGE_BASE+8;
+    
+    // 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 mComputeSizes = false;
+    // 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 static final int DLG_BASE = 0;
+    private static final int DLG_LOADING = DLG_BASE + 1;
+    
+    // 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;
+    
+    // empty message displayed when list is empty
+    private TextView mEmptyView;
+    
+    // Boolean variables indicating state
+    private boolean mLoadLabels = false;
+    private boolean mSizesFirst = false;
+    // ListView used to display list
+    private ListView mListView;
+    // State variables used to figure out menu options and also
+    // initiate the first computation and loading of resources
+    private boolean mJustCreated = true;
+    private boolean mFirst = false;
+    
+    /*
+     * 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 = null;
+            AppInfo appInfo;
+            data = msg.getData();
+            if(data != null) {
+                pkgName = data.getString(ATTR_PKG_NAME);
+            }
+            switch (msg.what) {
+            case INIT_PKG_INFO:
+                if(localLOGV) Log.i(TAG, "Message INIT_PKG_INFO");
+                // Retrieve the package list and init some structures
+                initAppList(mFilterApps);
+                mHandler.sendEmptyMessage(NEXT_LOAD_STEP);
+                break;
+            case COMPUTE_PKG_SIZE_DONE:
+                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;
+                    }
+                    mAppInfoAdapter.updateAppSize(pkgName, ps);
+                }
+                mComputeIndex++;
+                if (mComputeIndex < mAppInfoAdapter.getCount()) {
+                    // initiate compute package size for next pkg in list
+                    mObserver.invokeGetSizeInfo(mAppInfoAdapter.getApplicationInfo(
+                            mComputeIndex), 
+                            COMPUTE_PKG_SIZE_DONE);
+                } else {
+                    // 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;
+                        }
+                    }
+                    // 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
+                        mComputeSizes = true;
+                        mFirst = true;
+                        mAppInfoAdapter.sortList(mSortOrder);
+                        mHandler.sendEmptyMessage(NEXT_LOAD_STEP);
+                    }
+                }
+                break;
+            case REMOVE_PKG:
+                if(localLOGV) Log.i(TAG, "Message REMOVE_PKG");
+                if(pkgName == null) {
+                    Log.w(TAG, "Ignoring message:REMOVE_PKG for null pkgName");
+                    break;
+                }
+                if (!mComputeSizes) {
+                    Boolean currB = mAddRemoveMap.get(pkgName);
+                    if (currB == null || (currB.equals(Boolean.TRUE))) {
+                        mAddRemoveMap.put(pkgName, Boolean.FALSE);
+                    }
+                    break;
+                }
+                List<String> pkgList = new ArrayList<String>();
+                pkgList.add(pkgName);
+                mAppInfoAdapter.removeFromList(pkgList);
+                break;
+            case REORDER_LIST:
+                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(INIT_PKG_INFO);
+                        sendMessageToHandler(REORDER_LIST, menuOption);
+                    }
+                }
+                break;
+            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;
+                }
+                if (!mComputeSizes) {
+                    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);   
+                }
+                mLoadLabels = true;
+                mHandler.sendEmptyMessage(NEXT_LOAD_STEP);
+                break;
+            case NEXT_LOAD_STEP:
+                if (mComputeSizes && mLoadLabels) {
+                    doneLoadingData();
+                } else if (!mComputeSizes && !mLoadLabels) {
+                     // Either load the package labels or initiate get size info
+                    if (mSizesFirst) {
+                        initComputeSizes();
+                    } else {
+                        initResourceThread();
+                    }
+                } else {
+                    // Create list view from the adapter here. Wait till the sort order
+                    // of list is defined. its either by label or by size. so atleast one of the
+                    // first steps should be complete before filling the list
+                    if (mJustCreated) {
+                        // Set the adapter here.
+                        mJustCreated = false;
+                        mListView.setAdapter(mAppInfoAdapter);
+                        dismissLoadingMsg();
+                    }
+                    if (!mComputeSizes) {
+                        initComputeSizes();
+                    } else if (!mLoadLabels) {
+                        initResourceThread();
+                    }
+                }
+                break;
+            default:
+                break;
+            }
+        }
+    };
+    
+    
+    
+    private void doneLoadingData() {
+        setProgressBarIndeterminateVisibility(false);
+    }
+    
+    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) {
+                boolean flag = false;
+                if ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+                    // Updated system app
+                    flag = true;
+                } else if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                    // Non-system app
+                    flag = true;
+                }
+                if (flag) {
+                    appList.add(appInfo);
+                }
+            }
+            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 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) {
+        setProgressBarIndeterminateVisibility(true);
+        mComputeIndex = 0;
+        mComputeSizes = false;
+        mLoadLabels = false;
+        // Initialize lists
+        List<ApplicationInfo> appList = getInstalledApps(filterOption);
+        mAddRemoveMap = new TreeMap<String, Boolean>();
+        mAppInfoAdapter.resetAppList(filterOption, appList);
+    }
+    
+    // Utility method to start a thread to read application labels and icons
+    private void initResourceThread() {
+        //load resources now
+        if(mResourceThread.isAlive()) {
+            mResourceThread.interrupt();
+        }
+        mResourceThread.loadAllResources(mAppInfoAdapter.getAppList());
+    }
+    
+    private void initComputeSizes() {
+         // initiate compute pkg sizes
+        if (localLOGV) Log.i(TAG, "Initiating compute sizes for first time");
+        if (mAppInfoAdapter.getCount() > 0) {
+            mObserver.invokeGetSizeInfo(mAppInfoAdapter.getApplicationInfo(0),
+                    COMPUTE_PKG_SIZE_DONE);
+        } else {
+            mComputeSizes = true;
+        }
+    }
+    
+    private void showEmptyViewIfListEmpty() {
+        if (localLOGV) Log.i(TAG, "Checking for empty view");
+        if (mAppInfoAdapter.getCount() > 0) {
+            mListView.setVisibility(View.VISIBLE);
+            mEmptyView.setVisibility(View.GONE);
+        } else {
+            mListView.setVisibility(View.GONE);
+            mEmptyView.setVisibility(View.VISIBLE);
+        }
+    }
+    
+    // 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) {
+            mAppList = appList;
+            start();
+        }
+
+        public void run() {
+            Map<String, AppInfo> iconMap = new HashMap<String, AppInfo>();
+            if(mAppList == null || mAppList.size() <= 0) {
+                Log.w(TAG, "Empty or null application list");
+            } else {
+                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);
+        }
+    }
+    
+    /* 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;
+        }
+    }
+    
+    // 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 AppInfo getFromCache(String packageName) {
+            if(mAppPropCache == null) {
+                return null;
+            }
+            return mAppPropCache.get(packageName);
+        }
+        
+        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;
+                }
+            }
+            return -1;
+        }
+        
+        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) {
+            if (position >= mAppLocalList.size()) {
+                Log.w(TAG, "Invalid view position:"+position+", actual size is:"+mAppLocalList.size());
+                return null;
+            }
+            // 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);
+                }
+                if (mInfo.appSize != null) {
+                    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);
+               }
+           }
+           if (mAppLocalList.size() > 0) {
+               sortList(mSortOrder);
+           } else {
+               notifyDataSetChanged();
+           }
+           showEmptyViewIfListEmpty();
+           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);
+                    if (aInfo != null) {
+                        aInfo.refreshIcon(pInfo);
+                    } else {
+                        mAppPropMap.put(info.packageName, 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(INIT_PKG_INFO);
+        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);
+        }
+    }
+    
+    /**
+     * Receives notifications when applications are added/removed.
+     */
+    private class PackageIntentReceiver extends BroadcastReceiver {
+         void registerReceiver() {
+             IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+             filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+             filter.addDataScheme("package");
+             ManageApplications.this.registerReceiver(this, filter);
+         }
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String actionStr = intent.getAction();
+            Uri data = intent.getData();
+            String pkgName = data.getEncodedSchemeSpecificPart();
+            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)) {
+            Bundle data = new Bundle();
+            data.putString(ATTR_PKG_NAME, pkgName);
+            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);
+        }
+    }
+    
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent lIntent = getIntent();
+        String action = lIntent.getAction();
+        if (action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
+            mSortOrder = SORT_ORDER_SIZE;
+            mSizesFirst = true;
+        }
+        mPm = getPackageManager();
+        // initialize some window features
+        requestWindowFeature(Window.FEATURE_RIGHT_ICON);
+        requestWindowFeature(Window.FEATURE_PROGRESS);
+        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setContentView(R.layout.compute_sizes);
+        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);
+        mReceiver = new PackageIntentReceiver();
+        mEmptyView = (TextView) findViewById(R.id.empty_view);
+        mObserver = new PkgSizeObserver();
+        // Create adapter and list view here
+        List<ApplicationInfo> appList = getInstalledApps(mSortOrder);
+        mAppInfoAdapter = new AppInfoAdapter(this, appList);
+        ListView lv= (ListView) findViewById(android.R.id.list);
+        //lv.setAdapter(mAppInfoAdapter);
+        lv.setOnItemClickListener(this);
+        lv.setSaveEnabled(true);
+        lv.setItemsCanFocus(true);
+        lv.setOnItemClickListener(this);
+        mListView = lv;
+        showLoadingMsg();
+    }
+    
+    @Override
+    public Dialog onCreateDialog(int id) {
+        if (id == DLG_LOADING) {
+            ProgressDialog dlg = new ProgressDialog(this);
+            dlg.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+            dlg.setMessage(getText(R.string.loading));
+            dlg.setIndeterminate(true);        
+            dlg.setOnCancelListener(this);
+            return dlg;
+        }
+        return null;
+    }
+    
+    
+    private void showLoadingMsg() {
+        showDialog(DLG_LOADING);
+        if(localLOGV) Log.i(TAG, "Displaying Loading message");
+    }
+    
+    private void dismissLoadingMsg() {
+        if(localLOGV) Log.i(TAG, "Dismissing Loading message");
+        dismissDialog(DLG_LOADING);
+    }
+    
+    @Override
+    public void onStart() {
+        super.onStart();
+        // Create a thread to load resources
+        mResourceThread = new ResourceLoaderThread();
+        sendMessageToHandler(INIT_PKG_INFO);
+        // register receiver
+        mReceiver.registerReceiver();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        // clear all messages related to application list
+        clearMessagesInHandler();
+        // register receiver here
+        unregisterReceiver(mReceiver);        
+        mAppPropCache = mAppInfoAdapter.mAppPropMap;
+    }
+    
+    // Avoid the restart and pause when orientation changes
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+    }
+    
+    /*
+     * comparator class used to sort AppInfo objects based on size
+     */
+    public static class AppInfoComparator implements Comparator<ApplicationInfo> {
+        public AppInfoComparator(Map<String, AppInfo> pAppPropMap) {
+            mAppPropMap= pAppPropMap;
+        }
+
+        public final int compare(ApplicationInfo a, ApplicationInfo b) {
+            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) {
+                return 0;
+            }
+            return -1;
+        }
+        private Map<String, AppInfo> mAppPropMap;
+    }
+     
+    // utility method used to start sub activity
+    private void startApplicationDetailsActivity() {
+        // Create intent to start new activity
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setClass(this, InstalledAppDetails.class);
+        intent.putExtra(APP_PKG_NAME, mCurrentPkgName);
+        // start new activity to display extended information
+        startActivityForResult(intent, INSTALLED_APP_DETAILS);
+    }
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        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, 2, R.string.sort_order_size)
+                .setIcon(android.R.drawable.ic_menu_sort_by_size); 
+        menu.add(0, FILTER_OPTIONS, 3, R.string.filter)
+                .setIcon(R.drawable.ic_menu_filter_settings);
+        return true;
+    }
+    
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        if (mFirst) {
+            menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA);
+            menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE);
+            menu.findItem(FILTER_OPTIONS).setVisible(true);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        int menuId = item.getItemId();
+        if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) {
+            sendMessageToHandler(REORDER_LIST, menuId);
+        } else if (menuId == FILTER_OPTIONS) {
+            if (mAlertDlg == null) {
+                mAlertDlg = new AlertDialog.Builder(this).
+                        setTitle(R.string.filter_dlg_title).
+                        setNeutralButton(R.string.cancel, this).
+                        setSingleChoiceItems(new CharSequence[] {getText(R.string.filter_apps_all),
+                                getText(R.string.filter_apps_running),
+                                getText(R.string.filter_apps_third_party)},
+                                -1, this).
+                        create();
+            }
+            mAlertDlg.show();
+        }
+        return true;
+    }
+
+    public void onItemClick(AdapterView<?> parent, View view, int position,
+            long id) {
+        ApplicationInfo info = (ApplicationInfo)mAppInfoAdapter.getItem(position);
+        mCurrentPkgName = info.packageName;
+        startApplicationDetailsActivity();
+    }
+    
+    // Finish the activity if the user presses the back button to cancel the activity
+    public void onCancel(DialogInterface dialog) {
+        finish();
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        int newOption;
+        switch (which) {
+        // Make sure that values of 0, 1, 2 match options all, running, third_party when
+        // created via the AlertDialog.Builder
+        case 0:
+            newOption = FILTER_APPS_ALL;
+            break;
+        case 1:
+            newOption = FILTER_APPS_RUNNING;
+            break;
+        case 2:
+            newOption = FILTER_APPS_THIRD_PARTY;
+            break;
+        default:
+            return;
+        }
+        mAlertDlg.dismiss();
+        sendMessageToHandler(REORDER_LIST, newOption);
+    }
+}
diff --git a/src/com/android/settings/MasterClear.java b/src/com/android/settings/MasterClear.java
new file mode 100644
index 0000000..38ad608
--- /dev/null
+++ b/src/com/android/settings/MasterClear.java
@@ -0,0 +1,205 @@
+/*
+ * 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 com.android.internal.widget.LockPatternUtils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ICheckinService;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * Confirm and execute a reset of the device to a clean "just out of the box"
+ * state.  Multiple confirmations are required: first, a general "are you sure
+ * you want to do this?" prompt, followed by a keyguard pattern trace if the user
+ * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
+ * ON THE PHONE" prompt.  If at any time the phone is allowed to go to sleep, is
+ * locked, et cetera, then the confirmation sequence is abandoned.
+ */
+public class MasterClear extends Activity {
+
+    private static final int KEYGUARD_REQUEST = 55;
+
+    private LayoutInflater mInflater;
+    private LockPatternUtils mLockUtils;
+
+    private View mInitialView;
+    private Button mInitiateButton;
+
+    private View mFinalView;
+    private Button mFinalButton;
+
+    /** 
+     * The user has gone through the multiple confirmation, so now we go ahead
+     * and invoke the Checkin Service to reset the device to its factory-default
+     * state (rebooting in the process).
+     */
+    private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
+            public void onClick(View v) {
+                
+                // Those monkeys kept committing suicide, so we add this property
+                // to disable going through with the master clear
+                if (!TextUtils.isEmpty(SystemProperties.get("ro.monkey"))) {
+                    return;
+                }
+                
+                ICheckinService service = 
+                        ICheckinService.Stub.asInterface(ServiceManager.getService("checkin"));
+                if (service != null) {
+                    try {
+                        // This RPC should never return
+                        service.masterClear();
+                    } catch (android.os.RemoteException e) {
+                        // Intentionally blank - there's nothing we can do here
+                        Log.w("MasterClear", "Unable to invoke ICheckinService.masterClear()");
+                    }
+                } else {
+                    Log.w("MasterClear", "Unable to locate ICheckinService");
+                }
+
+                /* If we reach this point, the master clear didn't happen -- the
+                 * service might have been unregistered with the ServiceManager,
+                 * the RPC might have thrown an exception, or for some reason
+                 * the implementation of masterClear() may have returned instead
+                 * of resetting the device.
+                 */
+                new AlertDialog.Builder(MasterClear.this)
+                        .setMessage(getText(R.string.master_clear_failed))
+                        .setPositiveButton(getText(android.R.string.ok), null)
+                        .show();
+            }
+        };
+
+    /**
+     *  Keyguard validation is run using the standard {@link ConfirmLockPattern}
+     * component as a subactivity
+     */
+    private void runKeyguardConfirmation() {
+        final Intent intent = new Intent();
+        intent.setClassName("com.android.settings",
+                "com.android.settings.ConfirmLockPattern");
+        // supply header and footer text in the intent
+        intent.putExtra(ConfirmLockPattern.HEADER_TEXT,
+                getText(R.string.master_clear_gesture_prompt));
+        intent.putExtra(ConfirmLockPattern.FOOTER_TEXT,
+                getText(R.string.master_clear_gesture_explanation));
+        startActivityForResult(intent, KEYGUARD_REQUEST);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode != KEYGUARD_REQUEST) {
+            return;
+        }
+
+        // If the user entered a valid keyguard trace, present the final
+        // confirmation prompt; otherwise, go back to the initial state.
+        if (resultCode == Activity.RESULT_OK) {
+            establishFinalConfirmationState();
+        } else {
+            establishInitialState();
+        }
+    }
+
+    /**
+     * If the user clicks to begin the reset sequence, we next require a
+     * keyguard confirmation if the user has currently enabled one.  If there
+     * is no keyguard available, we simply go to the final confirmation prompt.
+     */
+    private Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
+            public void onClick(View v) {
+                if (mLockUtils.isLockPatternEnabled()) {
+                    runKeyguardConfirmation();
+                } else {
+                    establishFinalConfirmationState();
+                }
+            }
+        };
+
+    /**
+     * Configure the UI for the final confirmation interaction
+     */
+    private void establishFinalConfirmationState() {
+        if (mFinalView == null) {
+            mFinalView = mInflater.inflate(R.layout.master_clear_final, null);
+            mFinalButton =
+                    (Button) mFinalView.findViewById(R.id.execute_master_clear);
+            mFinalButton.setOnClickListener(mFinalClickListener);
+        }
+
+        setContentView(mFinalView);
+    }
+
+    /**
+     * In its initial state, the activity presents a button for the user to
+     * click in order to initiate a confirmation sequence.  This method is
+     * called from various other points in the code to reset the activity to
+     * this base state.
+     * 
+     * <p>Reinflating views from resources is expensive and prevents us from
+     * caching widget pointers, so we use a single-inflate pattern:  we lazy-
+     * inflate each view, caching all of the widget pointers we'll need at the
+     * time, then simply reuse the inflated views directly whenever we need
+     * to change contents.
+     */
+    private void establishInitialState() {
+        if (mInitialView == null) {
+            mInitialView = mInflater.inflate(R.layout.master_clear_primary, null);
+            mInitiateButton =
+                    (Button) mInitialView.findViewById(R.id.initiate_master_clear);
+            mInitiateButton.setOnClickListener(mInitiateListener);
+        }
+
+        setContentView(mInitialView);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        mInitialView = null;
+        mFinalView = null;
+        mInflater = LayoutInflater.from(this);
+        mLockUtils = new LockPatternUtils(getContentResolver());
+
+        establishInitialState();
+    }
+
+    /** Abandon all progress through the confirmation sequence by returning
+     * to the initial view any time the activity is interrupted (e.g. by
+     * idle timeout).
+     */
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        establishInitialState();
+    }
+
+}
diff --git a/src/com/android/settings/MediaFormat.java b/src/com/android/settings/MediaFormat.java
new file mode 100644
index 0000000..3594572
--- /dev/null
+++ b/src/com/android/settings/MediaFormat.java
@@ -0,0 +1,193 @@
+/*
+ * 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 com.android.internal.widget.LockPatternUtils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IMountService;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * Confirm and execute a format of the sdcard.
+ * Multiple confirmations are required: first, a general "are you sure
+ * you want to do this?" prompt, followed by a keyguard pattern trace if the user
+ * has defined one, followed by a final strongly-worded "THIS WILL ERASE EVERYTHING
+ * ON THE SD CARD" prompt.  If at any time the phone is allowed to go to sleep, is
+ * locked, et cetera, then the confirmation sequence is abandoned.
+ */
+public class MediaFormat extends Activity {
+
+    private static final int KEYGUARD_REQUEST = 55;
+
+    private LayoutInflater mInflater;
+    private LockPatternUtils mLockUtils;
+
+    private View mInitialView;
+    private Button mInitiateButton;
+
+    private View mFinalView;
+    private Button mFinalButton;
+
+    /** 
+     * The user has gone through the multiple confirmation, so now we go ahead
+     * and invoke the Mount Service to format the SD card.
+     */
+    private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
+            public void onClick(View v) {
+                
+                // Those monkeys kept committing suicide, so we add this property
+                // to disable going through with the format
+                if (!TextUtils.isEmpty(SystemProperties.get("ro.monkey"))) {
+                    return;
+                }
+                IMountService service =
+                        IMountService.Stub.asInterface(ServiceManager.getService("mount"));
+                if (service != null) {
+                    try {
+                        service.formatMedia(Environment.getExternalStorageDirectory().toString());
+                    } catch (android.os.RemoteException e) {
+                        // Intentionally blank - there's nothing we can do here
+                        Log.w("MediaFormat", "Unable to invoke IMountService.formatMedia()");
+                    }
+                } else {
+                    Log.w("MediaFormat", "Unable to locate IMountService");
+                }
+            finish();
+            }
+        };
+
+    /**
+     *  Keyguard validation is run using the standard {@link ConfirmLockPattern}
+     * component as a subactivity
+     */
+    private void runKeyguardConfirmation() {
+        final Intent intent = new Intent();
+        intent.setClassName("com.android.settings",
+                "com.android.settings.ConfirmLockPattern");
+        // supply header and footer text in the intent
+        intent.putExtra(ConfirmLockPattern.HEADER_TEXT,
+                getText(R.string.media_format_gesture_prompt));
+        intent.putExtra(ConfirmLockPattern.FOOTER_TEXT,
+                getText(R.string.media_format_gesture_explanation));
+        startActivityForResult(intent, KEYGUARD_REQUEST);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        if (requestCode != KEYGUARD_REQUEST) {
+            return;
+        }
+
+        // If the user entered a valid keyguard trace, present the final
+        // confirmation prompt; otherwise, go back to the initial state.
+        if (resultCode == Activity.RESULT_OK) {
+            establishFinalConfirmationState();
+        } else {
+            establishInitialState();
+        }
+    }
+
+    /**
+     * If the user clicks to begin the reset sequence, we next require a
+     * keyguard confirmation if the user has currently enabled one.  If there
+     * is no keyguard available, we simply go to the final confirmation prompt.
+     */
+    private Button.OnClickListener mInitiateListener = new Button.OnClickListener() {
+            public void onClick(View v) {
+                if (mLockUtils.isLockPatternEnabled()) {
+                    runKeyguardConfirmation();
+                } else {
+                    establishFinalConfirmationState();
+                }
+            }
+        };
+
+    /**
+     * Configure the UI for the final confirmation interaction
+     */
+    private void establishFinalConfirmationState() {
+        if (mFinalView == null) {
+            mFinalView = mInflater.inflate(R.layout.media_format_final, null);
+            mFinalButton =
+                    (Button) mFinalView.findViewById(R.id.execute_media_format);
+            mFinalButton.setOnClickListener(mFinalClickListener);
+        }
+
+        setContentView(mFinalView);
+    }
+
+    /**
+     * In its initial state, the activity presents a button for the user to
+     * click in order to initiate a confirmation sequence.  This method is
+     * called from various other points in the code to reset the activity to
+     * this base state.
+     * 
+     * <p>Reinflating views from resources is expensive and prevents us from
+     * caching widget pointers, so we use a single-inflate pattern:  we lazy-
+     * inflate each view, caching all of the widget pointers we'll need at the
+     * time, then simply reuse the inflated views directly whenever we need
+     * to change contents.
+     */
+    private void establishInitialState() {
+        if (mInitialView == null) {
+            mInitialView = mInflater.inflate(R.layout.media_format_primary, null);
+            mInitiateButton =
+                    (Button) mInitialView.findViewById(R.id.initiate_media_format);
+            mInitiateButton.setOnClickListener(mInitiateListener);
+        }
+
+        setContentView(mInitialView);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        mInitialView = null;
+        mFinalView = null;
+        mInflater = LayoutInflater.from(this);
+        mLockUtils = new LockPatternUtils(getContentResolver());
+
+        establishInitialState();
+    }
+
+    /** Abandon all progress through the confirmation sequence by returning
+     * to the initial view any time the activity is interrupted (e.g. by
+     * idle timeout).
+     */
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        establishInitialState();
+    }
+
+}
diff --git a/src/com/android/settings/ProgressCategory.java b/src/com/android/settings/ProgressCategory.java
new file mode 100644
index 0000000..15810b3
--- /dev/null
+++ b/src/com/android/settings/ProgressCategory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.preference.PreferenceCategory;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.Map;
+
+public class ProgressCategory extends PreferenceCategory {
+
+    private boolean mProgress = false;
+    
+    public ProgressCategory(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setLayoutResource(R.layout.preference_progress_category);
+    }
+    
+    @Override
+    public void onBindView(View view) {
+        super.onBindView(view);
+        View textView = view.findViewById(R.id.scanning_text);
+        View progressBar = view.findViewById(R.id.scanning_progress);
+
+        int visibility = mProgress ? View.VISIBLE : View.INVISIBLE;
+        textView.setVisibility(visibility);
+        progressBar.setVisibility(visibility);
+    }
+    
+    /**
+     * Turn on/off the progress indicator and text on the right.
+     * @param progressOn whether or not the progress should be displayed 
+     */
+    public void setProgress(boolean progressOn) {
+        mProgress = progressOn;
+        notifyChanged();
+    }
+}
+
diff --git a/src/com/android/settings/ProxySelector.java b/src/com/android/settings/ProxySelector.java
new file mode 100644
index 0000000..80fe3c9
--- /dev/null
+++ b/src/com/android/settings/ProxySelector.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2006 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.app.AlertDialog;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Proxy;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * To start the Proxy Selector activity, create the following intent.
+ *
+ * <code>
+ *      Intent intent = new Intent();
+ *      intent.setClassName("com.android.browser.ProxySelector");
+ *      startActivity(intent);
+ * </code>
+ *
+ * you can add extra options to the intent by using
+ *
+ * <code>
+ *   intent.putExtra(key, value);
+ * </code>
+ *
+ * the extra options are:
+ *
+ * button-label: a string label to display for the okay button
+ * title:        the title of the window
+ * error-text:   If not null, will be used as the label of the error message.
+ */
+public class ProxySelector extends Activity
+{
+    private final static String LOGTAG = "Settings";
+
+    EditText    mHostnameField;
+    EditText    mPortField;
+    Button      mOKButton;
+
+    // Matches blank input, ips, and domain names
+    private static final String HOSTNAME_REGEXP = "^$|^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*$";
+    private static final Pattern HOSTNAME_PATTERN;
+    static {
+        HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
+    }
+
+
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        if (android.util.Config.LOGV) Log.v(LOGTAG, "[ProxySelector] onStart");
+
+        setContentView(R.layout.proxy);
+        initView();
+        populateFields(false);
+    }
+
+    protected void showError(int error) {
+
+        new AlertDialog.Builder(this)
+                .setTitle(R.string.proxy_error)
+                .setMessage(error)
+                .setPositiveButton(R.string.proxy_error_dismiss, null)
+                .show();
+    }
+
+    void initView() {
+
+        mHostnameField = (EditText)findViewById(R.id.hostname);
+        mHostnameField.setOnFocusChangeListener(mOnFocusChangeHandler);
+
+        mPortField = (EditText)findViewById(R.id.port);
+        mPortField.setOnClickListener(mOKHandler);
+        mPortField.setOnFocusChangeListener(mOnFocusChangeHandler);
+
+        mOKButton = (Button)findViewById(R.id.action);
+        mOKButton.setOnClickListener(mOKHandler);
+
+        Button b = (Button)findViewById(R.id.clear);
+        b.setOnClickListener(mClearHandler);
+
+        b = (Button)findViewById(R.id.defaultView);
+        b.setOnClickListener(mDefaultHandler);
+    }
+
+    void populateFields(boolean useDefault) {
+        String hostname = null;
+        int port = -1;
+        if (useDefault) {
+            // Use the default proxy settings provided by the carrier
+            hostname = Proxy.getDefaultHost();
+            port = Proxy.getDefaultPort();
+        } else {
+            // Use the last setting given by the user
+            hostname = Proxy.getHost(this);
+            port = Proxy.getPort(this);
+        }
+
+        if (hostname == null) {
+            hostname = "";
+        }
+
+        mHostnameField.setText(hostname);
+
+        String portStr = port == -1 ? "" : Integer.toString(port);
+        mPortField.setText(portStr);
+
+        Intent intent = getIntent();
+
+        String buttonLabel = intent.getStringExtra("button-label");
+        if (!TextUtils.isEmpty(buttonLabel)) {
+            mOKButton.setText(buttonLabel);
+        }
+
+        String title = intent.getStringExtra("title");
+        if (!TextUtils.isEmpty(title)) {
+            setTitle(title);
+        }
+    }
+
+    /**
+     * validate syntax of hostname and port entries
+     * @return 0 on success, string resource ID on failure
+     */
+    int validate(String hostname, String port) {
+        Matcher match = HOSTNAME_PATTERN.matcher(hostname);
+
+        if (!match.matches()) return R.string.proxy_error_invalid_host;
+
+        if (hostname.length() > 0 && port.length() == 0) {
+            return R.string.proxy_error_empty_port;
+        }
+
+        if (port.length() > 0) {
+            if (hostname.length() == 0) {
+                return R.string.proxy_error_empty_host_set_port;
+            }
+            int portVal = -1;
+            try {
+                portVal = Integer.parseInt(port);
+            } catch (NumberFormatException ex) {
+                return R.string.proxy_error_invalid_port;
+            }
+            if (portVal <= 0 || portVal > 0xFFFF) {
+                return R.string.proxy_error_invalid_port;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * returns true on success, false if the user must correct something
+     */
+    boolean saveToDb() {
+
+        String hostname = mHostnameField.getText().toString().trim();
+        String portStr = mPortField.getText().toString().trim();
+        int port = -1;
+
+        int result = validate(hostname, portStr);
+        if (result > 0) {
+            showError(result);
+            return false;
+        }
+
+        if (portStr.length() > 0) {
+            try {
+                port = Integer.parseInt(portStr);
+            } catch (NumberFormatException ex) {
+                return false;
+            }
+        }
+
+        // FIXME: The best solution would be to make a better UI that would
+        // disable editing of the text boxes if the user chooses to use the
+        // default settings. i.e. checking a box to always use the default
+        // carrier. http:/b/issue?id=756480
+        // FIXME: This currently will not work if the default host is blank and
+        // the user has cleared the input boxes in order to not use a proxy.
+        // This is a UI problem and can be solved with some better form
+        // controls.
+        // FIXME: If the user types in a proxy that matches the default, should
+        // we keep that setting? Can be fixed with a new UI.
+        ContentResolver res = getContentResolver();
+        if (hostname.equals(Proxy.getDefaultHost())
+                && port == Proxy.getDefaultPort()) {
+            // If the user hit the default button and didn't change any of
+            // the input boxes, treat it as if the user has not specified a
+            // proxy.
+            hostname = null;
+        }
+
+        if (!TextUtils.isEmpty(hostname)) {
+            hostname += ':' + portStr;
+        }
+        Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY, hostname);
+        sendBroadcast(new Intent(Proxy.PROXY_CHANGE_ACTION));
+
+        return true;
+    }
+
+    OnClickListener mOKHandler = new OnClickListener() {
+            public void onClick(View v) {
+                if (saveToDb()) {
+                    finish();
+                }
+            }
+        };
+
+    OnClickListener mClearHandler = new OnClickListener() {
+            public void onClick(View v) {
+                mHostnameField.setText("");
+                mPortField.setText("");
+            }
+        };
+
+    OnClickListener mDefaultHandler = new OnClickListener() {
+            public void onClick(View v) {
+                populateFields(true);
+            }
+        };
+
+    OnFocusChangeListener mOnFocusChangeHandler = new OnFocusChangeListener() {
+            public void onFocusChange(View v, boolean hasFocus) {
+                if (hasFocus) {
+                    TextView textView = (TextView) v;
+                    Selection.selectAll((Spannable) textView.getText());
+                }
+            }
+        };
+}
diff --git a/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java
new file mode 100644
index 0000000..dbad45d
--- /dev/null
+++ b/src/com/android/settings/RadioInfo.java
@@ -0,0 +1,1184 @@
+/*
+ * Copyright (C) 2006 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.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.INetStatService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+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;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.EditText;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.PhoneStateIntentReceiver;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.gsm.PdpConnection;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+import java.io.ByteArrayOutputStream;
+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 {
+    private final String TAG = "phone";
+    
+    private static final int EVENT_PHONE_STATE_CHANGED = 100;
+    private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 200;
+    private static final int EVENT_SERVICE_STATE_CHANGED = 300;
+    private static final int EVENT_CFI_CHANGED = 302;
+
+    private static final int EVENT_QUERY_PREFERRED_TYPE_DONE = 1000;
+    private static final int EVENT_SET_PREFERRED_TYPE_DONE = 1001;
+    private static final int EVENT_QUERY_NEIGHBORING_CIDS_DONE = 1002;
+    private static final int EVENT_SET_QXDMLOG_DONE = 1003;
+    private static final int EVENT_SET_CIPHER_DONE = 1004;
+    private static final int EVENT_QUERY_SMSC_DONE = 1005;
+    private static final int EVENT_UPDATE_SMSC_DONE = 1006;
+
+    private static final int MENU_ITEM_SELECT_BAND  = 0;
+    private static final int MENU_ITEM_VIEW_ADN     = 1;
+    private static final int MENU_ITEM_VIEW_FDN     = 2;
+    private static final int MENU_ITEM_VIEW_SDN     = 3;
+    private static final int MENU_ITEM_GET_PDP_LIST = 4;
+    private static final int MENU_ITEM_TOGGLE_DATA  = 5;
+    private static final int MENU_ITEM_TOGGLE_DATA_ON_BOOT = 6;
+
+    private TextView mImei;
+    private TextView number;
+    private TextView callState;
+    private TextView operatorName;
+    private TextView roamingState;
+    private TextView gsmState;
+    private TextView gprsState;
+    private TextView network;
+    private TextView dBm;
+    private TextView mMwi;
+    private TextView mCfi;
+    private TextView mLocation;
+    private TextView mNeighboringCids;
+    private TextView resets;
+    private TextView attempts;
+    private TextView successes;
+    private TextView disconnects;
+    private TextView sentSinceReceived;
+    private TextView sent;
+    private TextView received;
+    private TextView mPingIpAddr;
+    private TextView mPingHostname;
+    private TextView mHttpClientTest;
+    private TextView cipherState;
+    private TextView dnsCheckState;
+    private EditText smsc;
+    private Button radioPowerButton;
+    private Button qxdmLogButton;
+    private Button cipherToggleButton;
+    private Button dnsCheckToggleButton;
+    private Button pingTestButton;
+    private Button updateSmscButton;
+    private Button refreshSmscButton;
+    private Spinner preferredNetworkType;
+
+    private TelephonyManager mTelephonyManager;
+    private Phone phone = null;
+    private PhoneStateIntentReceiver mPhoneStateReceiver;
+    private INetStatService netstat;
+
+    private OemCommands mOem = null;
+    private boolean mQxdmLogEnabled;
+    // The requested cipher state
+    private boolean mCipherOn;
+
+    private String mPingIpAddrResult;
+    private String mPingHostnameResult;
+    private String mHttpClientTestResult;
+    private boolean mMwiValue = false;
+    private boolean mCfiValue = false;
+
+    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onDataConnectionStateChanged(int state) {
+            updateDataState();
+            updateDataStats();
+            updatePdpList();
+            updateNetworkType();
+        }
+
+        @Override
+        public void onDataActivity(int direction) {
+            updateDataStats2();
+        }
+
+        @Override
+        public void onCellLocationChanged(CellLocation location) {
+            updateLocation(location);
+        }
+
+        @Override
+        public void onMessageWaitingIndicatorChanged(boolean mwi) {
+            mMwiValue = mwi;
+            updateMessageWaiting();
+        }
+
+        @Override
+        public void onCallForwardingIndicatorChanged(boolean cfi) {
+            mCfiValue = cfi;
+            updateCallRedirect();
+        }
+    };
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+            switch (msg.what) {
+                case EVENT_PHONE_STATE_CHANGED:
+                    updatePhoneState();
+                    break;
+
+                case EVENT_SIGNAL_STRENGTH_CHANGED:
+                    updateSignalStrength();
+                    break;
+
+                case EVENT_SERVICE_STATE_CHANGED:
+                    updateServiceState();
+                    updatePowerState();
+                    break;
+
+                case EVENT_QUERY_PREFERRED_TYPE_DONE:
+                    ar= (AsyncResult) msg.obj;
+                    if (ar.exception == null) {
+                        int type = ((int[])ar.result)[0];
+                        preferredNetworkType.setSelection(type, true);
+                    } else {
+                        preferredNetworkType.setSelection(3, true);
+                    }
+                    break;
+                case EVENT_SET_PREFERRED_TYPE_DONE:
+                    ar= (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        phone.getPreferredNetworkType(
+                                obtainMessage(EVENT_QUERY_PREFERRED_TYPE_DONE));
+                    }
+                    break;
+                case EVENT_QUERY_NEIGHBORING_CIDS_DONE:
+                    ar= (AsyncResult) msg.obj;
+                    if (ar.exception == null) {
+                        updateNeighboringCids((ArrayList<NeighboringCellInfo>)ar.result);
+                    } else {
+                        mNeighboringCids.setText("unknown");
+                    }
+                    break;
+                case EVENT_SET_QXDMLOG_DONE:
+                    ar= (AsyncResult) msg.obj;
+                    if (ar.exception == null) {
+                        mQxdmLogEnabled = !mQxdmLogEnabled;
+                        
+                        updateQxdmState(mQxdmLogEnabled);
+                        displayQxdmEnableResult();
+                    }
+                    break;
+                case EVENT_SET_CIPHER_DONE:
+                    ar= (AsyncResult) msg.obj;
+                    if (ar.exception == null) {
+                        setCiphPref(mCipherOn);
+                    }
+                    updateCiphState();
+                    break;
+                case EVENT_QUERY_SMSC_DONE:
+                    ar= (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        smsc.setText("refresh error");
+                    } else {
+                        byte[] buf = (byte[]) ar.result;
+                        smsc.setText(new String(buf));
+                    }
+                    break;
+                case EVENT_UPDATE_SMSC_DONE:
+                    updateSmscButton.setEnabled(true);
+                    ar= (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        smsc.setText("update error");
+                    }
+                    break;
+                default:
+                    break;
+
+            }
+        }
+    };
+
+    private class OemCommands {
+
+        public  final int OEM_QXDM_SDLOG_DEFAULT_FILE_SIZE = 32;
+        public  final int OEM_QXDM_SDLOG_DEFAULT_MASK = 0;
+        public  final int OEM_QXDM_SDLOG_DEFAULT_MAX_INDEX = 8;
+
+        final int SIZE_OF_INT = 4;
+        final int OEM_FEATURE_ENABLE = 1;
+        final int OEM_FEATURE_DISABLE = 0;
+        final int OEM_SIMPE_FEAUTURE_LEN = 1;
+
+        final int OEM_QXDM_SDLOG_FUNCTAG = 0x00010000;
+        final int OEM_QXDM_SDLOG_LEN = 4;
+        final int OEM_PS_AUTO_ATTACH_FUNCTAG = 0x00020000;
+        final int OEM_CIPHERING_FUNCTAG = 0x00020001;
+        final int OEM_SMSC_UPDATE_FUNCTAG = 0x00020002;
+        final int OEM_SMSC_QUERY_FUNCTAG = 0x00020003;
+        final int OEM_SMSC_QUERY_LEN = 0;
+        
+        /**
+         * The OEM interface to store QXDM to SD.
+         *
+         * To start/stop logging QXDM logs to SD card, use tag
+         * OEM_RIL_HOOK_QXDM_SD_LOG_SETUP 0x00010000
+         *
+         * "data" is a const oem_ril_hook_qxdm_sdlog_setup_data_st *
+         * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->head.func_tag
+         * should be OEM_RIL_HOOK_QXDM_SD_LOG_SETUP
+         * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->head.len
+         * should be "sizeof(unsigned int) * 4"
+         * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->mode
+         * could be 0 for 'stop logging', or 1 for 'start logging'
+         * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->log_file_size
+         * will assign the size of each log file, and it could be a value between
+         * 1 and 512 (in megabytes, default value is recommended to set as 32).
+         * This value will be ignored when mode == 0.
+         * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->log_mask will
+         * assign the rule to filter logs, and it is a bitmask (bit0 is for MsgAll,
+         * bit1 is for LogAll, and bit2 is for EventAll) recommended to be set as 0
+         * by default. This value will be ignored when mode == 0.
+         * ((const oem_ril_hook_qxdm_sdlog_setup_data_st *)data)->log_max_fileindex
+         * set the how many logfiles will storted before roll over. This value will
+         * be ignored when mode == 0.
+         *
+         * "response" is NULL
+         *
+         * typedef struct _oem_ril_hook_raw_head_st {
+         *      unsigned int func_tag;
+         *      unsigned int len;
+         * } oem_ril_hook_raw_head_st;
+         *
+         * typedef struct _oem_ril_hook_qxdm_sdlog_setup_data_st {
+         *      oem_ril_hook_raw_head_st head;
+         *      unsigned int mode;
+         *      unsigned int log_file_size;
+         *      unsigned int log_mask;
+         *      unsigned int log_max_fileindex;
+         * } oem_ril_hook_qxdm_sdlog_setup_data_st;
+         *
+         * @param enable set true to start logging QXDM in SD card
+         * @param fileSize is the log file size in MB
+         * @param mask is the log mask to filter
+         * @param maxIndex is the maximum roll-over file number
+         * @return byteArray to use in RIL RAW command
+         */
+        byte[] getQxdmSdlogData(boolean enable, int fileSize, int mask, int maxIndex) {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            DataOutputStream dos = new DataOutputStream(bos);
+            try {
+                writeIntLittleEndian(dos, OEM_QXDM_SDLOG_FUNCTAG);
+                writeIntLittleEndian(dos, OEM_QXDM_SDLOG_LEN * SIZE_OF_INT);
+                writeIntLittleEndian(dos, enable ?
+                        OEM_FEATURE_ENABLE : OEM_FEATURE_DISABLE);
+                writeIntLittleEndian(dos, fileSize);
+                writeIntLittleEndian(dos, mask);
+                writeIntLittleEndian(dos, maxIndex);
+            } catch (IOException e) {
+                return null;
+            }
+            return bos.toByteArray();
+        }
+
+        byte[] getSmscQueryData() {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            DataOutputStream dos = new DataOutputStream(bos);
+            try {
+                writeIntLittleEndian(dos, OEM_SMSC_QUERY_FUNCTAG);
+                writeIntLittleEndian(dos, OEM_SMSC_QUERY_LEN * SIZE_OF_INT);
+            } catch (IOException e) {
+                return null;
+            }
+            return bos.toByteArray();
+        }
+
+        byte[] getSmscUpdateData(String smsc) {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            DataOutputStream dos = new DataOutputStream(bos);
+            try {
+                byte[] smsc_bytes = smsc.getBytes();
+                writeIntLittleEndian(dos, OEM_SMSC_UPDATE_FUNCTAG);
+                writeIntLittleEndian(dos, smsc_bytes.length);
+                dos.write(smsc_bytes);
+            } catch (IOException e) {
+                return null;
+            }
+            return bos.toByteArray();
+        }
+
+        byte[] getPsAutoAttachData(boolean enable) {
+            return getSimpleFeatureData(OEM_PS_AUTO_ATTACH_FUNCTAG, enable);
+        }
+
+        byte[] getCipheringData(boolean enable) {
+            return getSimpleFeatureData(OEM_CIPHERING_FUNCTAG, enable);
+        }
+        
+        private byte[] getSimpleFeatureData(int tag, boolean enable) {
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            DataOutputStream dos = new DataOutputStream(bos);
+            try {
+                writeIntLittleEndian(dos, tag);
+                writeIntLittleEndian(dos, OEM_SIMPE_FEAUTURE_LEN * SIZE_OF_INT);
+                writeIntLittleEndian(dos, enable ?
+                        OEM_FEATURE_ENABLE : OEM_FEATURE_DISABLE);
+            } catch (IOException e) {
+                return null;
+            }
+            return bos.toByteArray();
+        }
+
+        private void writeIntLittleEndian(DataOutputStream dos, int val)
+                throws IOException {
+            dos.writeByte(val);
+            dos.writeByte(val >> 8);
+            dos.writeByte(val >> 16);
+            dos.writeByte(val >> 24);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.radio_info);
+
+        mTelephonyManager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
+        phone = PhoneFactory.getDefaultPhone();
+
+        mImei = (TextView) findViewById(R.id.imei);
+        number = (TextView) findViewById(R.id.number);
+        callState = (TextView) findViewById(R.id.call);
+        operatorName = (TextView) findViewById(R.id.operator);
+        roamingState = (TextView) findViewById(R.id.roaming);
+        gsmState = (TextView) findViewById(R.id.gsm);
+        gprsState = (TextView) findViewById(R.id.gprs);
+        network = (TextView) findViewById(R.id.network);
+        dBm = (TextView) findViewById(R.id.dbm);
+        mMwi = (TextView) findViewById(R.id.mwi);
+        mCfi = (TextView) findViewById(R.id.cfi);
+        mLocation = (TextView) findViewById(R.id.location);
+        mNeighboringCids = (TextView) findViewById(R.id.neighboring);
+
+        resets = (TextView) findViewById(R.id.resets);
+        attempts = (TextView) findViewById(R.id.attempts);
+        successes = (TextView) findViewById(R.id.successes);
+        disconnects = (TextView) findViewById(R.id.disconnects);
+        sentSinceReceived = (TextView) findViewById(R.id.sentSinceReceived);
+        sent = (TextView) findViewById(R.id.sent);
+        received = (TextView) findViewById(R.id.received);
+        cipherState = (TextView) findViewById(R.id.ciphState);
+        smsc = (EditText) findViewById(R.id.smsc);
+        dnsCheckState = (TextView) findViewById(R.id.dnsCheckState);
+
+        mPingIpAddr = (TextView) findViewById(R.id.pingIpAddr);
+        mPingHostname = (TextView) findViewById(R.id.pingHostname);
+        mHttpClientTest = (TextView) findViewById(R.id.httpClientTest);
+
+        preferredNetworkType = (Spinner) findViewById(R.id.preferredNetworkType);
+        ArrayAdapter<String> adapter = new ArrayAdapter<String> (this,
+                android.R.layout.simple_spinner_item, mPreferredNetworkLabels);
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);        
+        preferredNetworkType.setAdapter(adapter);
+        preferredNetworkType.setOnItemSelectedListener(mPreferredNetworkHandler);
+
+        radioPowerButton = (Button) findViewById(R.id.radio_power);
+        radioPowerButton.setOnClickListener(mPowerButtonHandler);
+
+        qxdmLogButton = (Button) findViewById(R.id.qxdm_log);
+        qxdmLogButton.setOnClickListener(mQxdmButtonHandler);
+
+        cipherToggleButton = (Button) findViewById(R.id.ciph_toggle);
+        cipherToggleButton.setOnClickListener(mCipherButtonHandler);
+        pingTestButton = (Button) findViewById(R.id.ping_test);
+        pingTestButton.setOnClickListener(mPingButtonHandler);
+        updateSmscButton = (Button) findViewById(R.id.update_smsc);
+        updateSmscButton.setOnClickListener(mUpdateSmscButtonHandler);
+        refreshSmscButton = (Button) findViewById(R.id.refresh_smsc);
+        refreshSmscButton.setOnClickListener(mRefreshSmscButtonHandler);
+        dnsCheckToggleButton = (Button) findViewById(R.id.dns_check_toggle);
+        dnsCheckToggleButton.setOnClickListener(mDnsCheckButtonHandler);
+        
+        mPhoneStateReceiver = new PhoneStateIntentReceiver(this, mHandler);
+        mPhoneStateReceiver.notifySignalStrength(EVENT_SIGNAL_STRENGTH_CHANGED);
+        mPhoneStateReceiver.notifyServiceState(EVENT_SERVICE_STATE_CHANGED);
+        mPhoneStateReceiver.notifyPhoneCallState(EVENT_PHONE_STATE_CHANGED);
+                         
+        updateQxdmState(null);
+        mOem = new OemCommands();
+
+        phone.getPreferredNetworkType(
+                mHandler.obtainMessage(EVENT_QUERY_PREFERRED_TYPE_DONE));
+        phone.getNeighboringCids(
+                mHandler.obtainMessage(EVENT_QUERY_NEIGHBORING_CIDS_DONE));
+
+        netstat = INetStatService.Stub.asInterface(ServiceManager.getService("netstat"));
+
+        CellLocation.requestLocationUpdate();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        updatePhoneState();
+        updateSignalStrength();
+        updateMessageWaiting();
+        updateCallRedirect();
+        updateServiceState();
+        updateLocation(mTelephonyManager.getCellLocation());
+        updateDataState();
+        updateDataStats();
+        updateDataStats2();
+        updatePowerState();
+        updateQxdmState(null);
+        updateProperties();
+        updateCiphState();
+        updateDnsCheckState();
+
+        Log.i(TAG, "[RadioInfo] onResume: register phone & data intents");
+
+        mPhoneStateReceiver.registerIntent();
+        mTelephonyManager.listen(mPhoneStateListener,
+                  PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
+                | PhoneStateListener.LISTEN_DATA_ACTIVITY
+                | PhoneStateListener.LISTEN_CELL_LOCATION
+                | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
+                | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        Log.i(TAG, "[RadioInfo] onPause: unregister phone & data intents");
+
+        mPhoneStateReceiver.unregisterIntent();
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        menu.add(0, MENU_ITEM_SELECT_BAND, 0, R.string.radio_info_band_mode_label).setOnMenuItemClickListener(mSelectBandCallback)
+                .setAlphabeticShortcut('b');
+        menu.add(1, MENU_ITEM_VIEW_ADN, 0,
+                R.string.radioInfo_menu_viewADN).setOnMenuItemClickListener(mViewADNCallback);
+        menu.add(1, MENU_ITEM_VIEW_FDN, 0,
+                R.string.radioInfo_menu_viewFDN).setOnMenuItemClickListener(mViewFDNCallback);
+        menu.add(1, MENU_ITEM_VIEW_SDN, 0,
+                R.string.radioInfo_menu_viewSDN).setOnMenuItemClickListener(mViewSDNCallback);
+        menu.add(1, MENU_ITEM_GET_PDP_LIST,
+                0, R.string.radioInfo_menu_getPDP).setOnMenuItemClickListener(mGetPdpList);
+        menu.add(1, MENU_ITEM_TOGGLE_DATA,
+                0, R.string.radioInfo_menu_disableData).setOnMenuItemClickListener(mToggleData);
+        menu.add(1, MENU_ITEM_TOGGLE_DATA_ON_BOOT,
+                0, R.string.radioInfo_menu_disableDataOnBoot).setOnMenuItemClickListener(mToggleDataOnBoot);
+        return true;
+    }
+
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu)
+    {
+        // Get the TOGGLE DATA menu item in the right state.
+        MenuItem item = menu.findItem(MENU_ITEM_TOGGLE_DATA);
+        int state = mTelephonyManager.getDataState();
+        boolean visible = true;
+
+        switch (state) {
+            case TelephonyManager.DATA_CONNECTED:
+            case TelephonyManager.DATA_SUSPENDED:
+                item.setTitle(R.string.radioInfo_menu_disableData);
+                break;
+            case TelephonyManager.DATA_DISCONNECTED:
+                item.setTitle(R.string.radioInfo_menu_enableData);
+                break;
+            default:
+                visible = false;
+                break;
+        }
+        item.setVisible(visible);
+
+        // Get the toggle-data-on-boot menu item in the right state.
+        item = menu.findItem(MENU_ITEM_TOGGLE_DATA_ON_BOOT);
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this.getApplication());
+        boolean value = sp.getBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, false);
+        if (value) {
+            item.setTitle(R.string.radioInfo_menu_enableDataOnBoot);
+        } else {
+            item.setTitle(R.string.radioInfo_menu_disableDataOnBoot);
+        }
+        return true;
+    }
+
+    private boolean isRadioOn() {
+        return phone.getServiceState().getState() != ServiceState.STATE_POWER_OFF;
+    }
+    
+    private void updatePowerState() {
+    	//log("updatePowerState");
+        String buttonText = isRadioOn() ?
+                            getString(R.string.turn_off_radio) :
+                            getString(R.string.turn_on_radio);
+        radioPowerButton.setText(buttonText);    	
+    }
+
+    private void updateQxdmState(Boolean newQxdmStatus) {
+        SharedPreferences sp = 
+          PreferenceManager.getDefaultSharedPreferences(this.getApplication());
+        mQxdmLogEnabled = sp.getBoolean("qxdmstatus", false);
+        // This is called from onCreate, onResume, and the handler when the status
+        // is updated. 
+        if (newQxdmStatus != null) {
+            SharedPreferences.Editor editor = sp.edit();
+            editor.putBoolean("qxdmstatus", newQxdmStatus);
+            editor.commit();
+            mQxdmLogEnabled = newQxdmStatus;
+        }
+        
+        String buttonText = mQxdmLogEnabled ?
+                            getString(R.string.turn_off_qxdm) :
+                            getString(R.string.turn_on_qxdm);
+        qxdmLogButton.setText(buttonText);
+    }
+
+    private void setCiphPref(boolean value) {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this.getApplication());
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putBoolean(GSMPhone.CIPHERING_KEY, value);
+        editor.commit();
+    }
+
+    private boolean getCiphPref() {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this.getApplication());
+        boolean ret = sp.getBoolean(GSMPhone.CIPHERING_KEY, true);
+        return ret;
+    }
+
+    private void updateCiphState() {
+        cipherState.setText(getCiphPref() ? "Ciphering ON" : "Ciphering OFF");
+    }
+
+    private void updateDnsCheckState() {
+        GSMPhone gsmPhone = (GSMPhone) phone;
+        dnsCheckState.setText(gsmPhone.isDnsCheckDisabled() ?
+                "0.0.0.0 allowed" :"0.0.0.0 not allowed");
+    }
+    
+    private final void
+    updateSignalStrength() {
+        int state =
+                mPhoneStateReceiver.getServiceState().getState();
+        Resources r = getResources();
+
+        if ((ServiceState.STATE_OUT_OF_SERVICE == state) ||
+                (ServiceState.STATE_POWER_OFF == state)) {
+            dBm.setText("0");
+        }
+        
+        int signalDbm = mPhoneStateReceiver.getSignalStrengthDbm();
+        
+        if (-1 == signalDbm) signalDbm = 0;
+
+        int signalAsu = mPhoneStateReceiver.getSignalStrength();
+
+        if (-1 == signalAsu) signalAsu = 0;
+
+        dBm.setText(String.valueOf(signalDbm) + " "
+            + r.getString(R.string.radioInfo_display_dbm) + "   "
+            + String.valueOf(signalAsu) + " "
+            + r.getString(R.string.radioInfo_display_asu));
+    }
+
+    private final void updateLocation(CellLocation location) {
+        GsmCellLocation loc = (GsmCellLocation)location;
+        Resources r = getResources();
+
+        int lac = loc.getLac();
+        int cid = loc.getCid();
+
+        mLocation.setText(r.getString(R.string.radioInfo_lac) + " = "
+                          + ((lac == -1) ? "unknown" : Integer.toHexString(lac))
+                          + "   "
+                          + r.getString(R.string.radioInfo_cid) + " = "
+                + ((cid == -1) ? "unknown" : Integer.toHexString(cid)));
+    }
+
+    private final void updateNeighboringCids(ArrayList<NeighboringCellInfo> cids) {
+        String neighborings = "";
+        if (cids != null) {
+            if ( cids.isEmpty() ) {
+                neighborings = "no neighboring cells";
+            } else {
+                for (NeighboringCellInfo cell : cids) {
+                    neighborings += "{" + Integer.toHexString(cell.getCid()) 
+                    + "@" + cell.getRssi() + "} ";
+                }
+            }
+        } else {
+            neighborings = "unknown";
+        }
+        mNeighboringCids.setText(neighborings);
+    }
+
+    private final void
+    updateMessageWaiting() {
+        mMwi.setText(String.valueOf(mMwiValue));
+    }
+
+    private final void
+    updateCallRedirect() {
+        mCfi.setText(String.valueOf(mCfiValue));
+    }
+
+
+    private final void
+    updateServiceState() {
+        ServiceState serviceState = mPhoneStateReceiver.getServiceState();
+        int state = serviceState.getState();
+        Resources r = getResources();
+        String display = r.getString(R.string.radioInfo_unknown);
+        
+        switch (state) {
+            case ServiceState.STATE_IN_SERVICE:
+                display = r.getString(R.string.radioInfo_service_in);
+                break;
+            case ServiceState.STATE_OUT_OF_SERVICE:
+            case ServiceState.STATE_EMERGENCY_ONLY:
+                display = r.getString(R.string.radioInfo_service_emergency);
+                break;
+            case ServiceState.STATE_POWER_OFF:
+                display = r.getString(R.string.radioInfo_service_off);
+                break;
+        }
+        
+        gsmState.setText(display);
+        
+        if (serviceState.getRoaming()) {
+            roamingState.setText(R.string.radioInfo_roaming_in);
+        } else {
+            roamingState.setText(R.string.radioInfo_roaming_not);
+        }
+
+        operatorName.setText(serviceState.getOperatorAlphaLong());
+    }
+
+    private final void
+    updatePhoneState() {
+        Phone.State state = mPhoneStateReceiver.getPhoneState();
+        Resources r = getResources();
+        String display = r.getString(R.string.radioInfo_unknown);
+
+        switch (state) {
+            case IDLE:
+                display = r.getString(R.string.radioInfo_phone_idle);
+                break;
+            case RINGING:
+                display = r.getString(R.string.radioInfo_phone_ringing);
+                break;
+            case OFFHOOK:
+                display = r.getString(R.string.radioInfo_phone_offhook);
+                break;
+        }
+
+        callState.setText(display);
+    }
+
+    private final void
+    updateDataState() {
+        int state = mTelephonyManager.getDataState();
+        Resources r = getResources();
+        String display = r.getString(R.string.radioInfo_unknown);
+
+        switch (state) {
+            case TelephonyManager.DATA_CONNECTED:
+                display = r.getString(R.string.radioInfo_data_connected);
+                break;
+            case TelephonyManager.DATA_CONNECTING:
+                display = r.getString(R.string.radioInfo_data_connecting);
+                break;
+            case TelephonyManager.DATA_DISCONNECTED:
+                display = r.getString(R.string.radioInfo_data_disconnected);
+                break;
+            case TelephonyManager.DATA_SUSPENDED:
+                display = r.getString(R.string.radioInfo_data_suspended);
+                break;
+        }
+        
+        gprsState.setText(display);
+    }
+
+    private final void updateNetworkType() {
+        Resources r = getResources();
+        String display = SystemProperties.get(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE,
+                r.getString(R.string.radioInfo_unknown));
+
+        network.setText(display);
+    }
+
+    private final void
+    updateProperties() {
+        String s;
+        Resources r = getResources();
+
+        s = phone.getDeviceId();
+        if (s == null) s = r.getString(R.string.radioInfo_unknown); 
+        mImei.setText(s);
+        
+        s = phone.getLine1Number();
+        if (s == null) s = r.getString(R.string.radioInfo_unknown); 
+        number.setText(s);
+    }
+
+    private final void updateDataStats() {
+        String s;
+
+        s = SystemProperties.get("net.gsm.radio-reset", "0");
+        resets.setText(s);
+
+        s = SystemProperties.get("net.gsm.attempt-gprs", "0");
+        attempts.setText(s);
+
+        s = SystemProperties.get("net.gsm.succeed-gprs", "0");
+        successes.setText(s);
+
+        //s = SystemProperties.get("net.gsm.disconnect", "0");
+        //disconnects.setText(s);
+
+        s = SystemProperties.get("net.ppp.reset-by-timeout", "0");
+        sentSinceReceived.setText(s);
+    }
+
+    private final void updateDataStats2() {
+        Resources r = getResources();
+
+        try {
+            long txPackets = netstat.getMobileTxPackets();
+            long rxPackets = netstat.getMobileRxPackets();
+            long txBytes   = netstat.getMobileTxBytes();
+            long rxBytes   = netstat.getMobileRxBytes();
+    
+            String packets = r.getString(R.string.radioInfo_display_packets);
+            String bytes   = r.getString(R.string.radioInfo_display_bytes);
+    
+            sent.setText(txPackets + " " + packets + ", " + txBytes + " " + bytes);
+            received.setText(rxPackets + " " + packets + ", " + rxBytes + " " + bytes);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Ping a IP address.
+     */
+    private final void pingIpAddr() {
+        try {
+            // This is hardcoded IP addr. This is for testing purposes.
+            // We would need to get rid of this before release.
+            String ipAddress = "74.125.47.104";
+            Process p = Runtime.getRuntime().exec("ping -c 1 " + ipAddress);
+            int status = p.waitFor();
+            if (status == 0) {
+                mPingIpAddrResult = "Pass";
+            } else {
+                mPingIpAddrResult = "Fail: IP addr not reachable";
+            }
+        } catch (IOException e) {
+            mPingIpAddrResult = "Fail: IOException";
+        } catch (InterruptedException e) {
+            mPingIpAddrResult = "Fail: InterruptedException";
+        }
+    }
+
+    /**
+     *  Ping a host name
+     */
+    private final void pingHostname() {
+        try {
+            Process p = Runtime.getRuntime().exec("ping -c 1 www.google.com"); 
+            int status = p.waitFor();
+            if (status == 0) {
+                mPingHostnameResult = "Pass";
+            } else {
+                mPingHostnameResult = "Fail: Host unreachable";
+            }
+        } catch (UnknownHostException e) {
+            mPingHostnameResult = "Fail: Unknown Host";
+        } catch (IOException e) {
+            mPingHostnameResult= "Fail: IOException";
+        } catch (InterruptedException e) {
+            mPingHostnameResult = "Fail: InterruptedException";
+        }
+    }
+
+    /**
+     * This function checks for basic functionality of HTTP Client.
+     */
+    private void httpClientTest() {
+        HttpClient client = new DefaultHttpClient();
+        try {
+            HttpGet request = new HttpGet("http://www.google.com");
+            HttpResponse response = client.execute(request);
+            if (response.getStatusLine().getStatusCode() == 200) {
+                mHttpClientTestResult = "Pass";
+            } else {
+                mHttpClientTestResult = "Fail: Code: " + String.valueOf(response);
+            }
+            request.abort();
+        } catch (IOException e) {
+            mHttpClientTestResult = "Fail: IOException";
+        }
+    }
+
+    private void refreshSmsc() {
+        byte[] data = mOem.getSmscQueryData();
+        if (data == null) return;
+        phone.invokeOemRilRequestRaw(data,
+                mHandler.obtainMessage(EVENT_QUERY_SMSC_DONE));
+    }
+
+    private final void updatePingState() {
+        final Handler handler = new Handler();
+        // Set all to unknown since the threads will take a few secs to update.
+        mPingIpAddrResult = getResources().getString(R.string.radioInfo_unknown);
+        mPingHostnameResult = getResources().getString(R.string.radioInfo_unknown);
+        mHttpClientTestResult = getResources().getString(R.string.radioInfo_unknown);
+
+        mPingIpAddr.setText(mPingIpAddrResult);
+        mPingHostname.setText(mPingHostnameResult);
+        mHttpClientTest.setText(mHttpClientTestResult);
+
+        final Runnable updatePingResults = new Runnable() {
+            public void run() {
+                mPingIpAddr.setText(mPingIpAddrResult);
+                mPingHostname.setText(mPingHostnameResult);
+                mHttpClientTest.setText(mHttpClientTestResult);
+            }
+        };
+        Thread ipAddr = new Thread() {
+            @Override
+            public void run() {
+                pingIpAddr();
+                handler.post(updatePingResults);
+            }
+        };
+        ipAddr.start();
+
+        Thread hostname = new Thread() {
+            @Override
+            public void run() {
+                pingHostname();
+                handler.post(updatePingResults);
+            }
+        };
+        hostname.start();
+
+        Thread httpClient = new Thread() {
+            @Override
+            public void run() {
+                httpClientTest();
+                handler.post(updatePingResults);
+            }
+        };
+        httpClient.start();
+    }
+
+    private final void updatePdpList() {
+        StringBuilder sb = new StringBuilder("========DATA=======\n");
+
+        List<PdpConnection> pdps = phone.getCurrentPdpList();
+
+        for (PdpConnection pdp : pdps) {
+            sb.append("    State: ").append(pdp.getState().toString()).append("\n");
+            if (pdp.getState().isActive()) {
+                long timeElapsed =
+                    (System.currentTimeMillis() - pdp.getConnectionTime())/1000;
+                sb.append("    connected at ")
+                  .append(DateUtils.timeString(pdp.getConnectionTime()))
+                  .append(" and elapsed ")
+                  .append(DateUtils.formatElapsedTime(timeElapsed))
+                  .append("\n    to ")
+                  .append(pdp.getApn().toString())
+                  .append("\ninterface: ")
+                  .append(phone.getInterfaceName(phone.getActiveApnTypes()[0]))
+                  .append("\naddress: ")
+                  .append(phone.getIpAddress(phone.getActiveApnTypes()[0]))
+                  .append("\ngateway: ")
+                  .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()))
+                  .append("\n    fail because ")
+                  .append(pdp.getLastFailCause().toString());
+            } else {
+                sb.append("    is connecting to ")
+                  .append(pdp.getApn().toString());
+            }
+            sb.append("\n===================");
+        }
+
+
+        disconnects.setText(sb.toString());
+    }
+
+    private void displayQxdmEnableResult() {
+        String status = mQxdmLogEnabled ? "Start QXDM Log" : "Stop QXDM Log";
+
+        DialogInterface mProgressPanel = new AlertDialog.
+                Builder(this).setMessage(status).show();
+
+        mHandler.postDelayed(
+                new Runnable() {
+                    public void run() {
+                        finish();
+                    }
+                }, 2000);
+    }
+
+    private MenuItem.OnMenuItemClickListener mViewADNCallback = new MenuItem.OnMenuItemClickListener() {
+        public boolean onMenuItemClick(MenuItem item) {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            // XXX We need to specify the component here because if we don't
+            // the activity manager will try to resolve the type by calling
+            // the content provider, which causes it to be loaded in a process
+            // other than the Dialer process, which causes a lot of stuff to
+            // break.
+            intent.setClassName("com.android.phone",
+                    "com.android.phone.SimContacts");
+            startActivity(intent);
+            return true;
+        }
+    };
+
+    private MenuItem.OnMenuItemClickListener mViewFDNCallback = new MenuItem.OnMenuItemClickListener() {
+        public boolean onMenuItemClick(MenuItem item) {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            // XXX We need to specify the component here because if we don't
+            // the activity manager will try to resolve the type by calling
+            // the content provider, which causes it to be loaded in a process
+            // other than the Dialer process, which causes a lot of stuff to
+            // break.
+            intent.setClassName("com.android.phone",
+                    "com.android.phone.FdnList");
+            startActivity(intent);
+            return true;
+        }
+    };
+
+    private MenuItem.OnMenuItemClickListener mViewSDNCallback = new MenuItem.OnMenuItemClickListener() {
+        public boolean onMenuItemClick(MenuItem item) {
+            Intent intent = new Intent(
+                Intent.ACTION_VIEW, Uri.parse("content://sim/sdn"));
+            // XXX We need to specify the component here because if we don't
+            // the activity manager will try to resolve the type by calling
+            // the content provider, which causes it to be loaded in a process
+            // other than the Dialer process, which causes a lot of stuff to
+            // break.
+            intent.setClassName("com.android.phone",
+                    "com.android.phone.ADNList");
+            startActivity(intent);
+            return true;
+        }
+    };
+
+    private void toggleDataDisabledOnBoot() {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this.getApplication());
+        SharedPreferences.Editor editor = sp.edit();
+        boolean value = sp.getBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, false);
+        editor.putBoolean(GSMPhone.DATA_DISABLED_ON_BOOT_KEY, !value);
+        byte[] data = mOem.getPsAutoAttachData(value);
+        if (data == null) {
+            // don't commit
+            return;
+        }
+
+        editor.commit();
+        phone.invokeOemRilRequestRaw(data, null);
+    }
+
+    private MenuItem.OnMenuItemClickListener mToggleDataOnBoot = new MenuItem.OnMenuItemClickListener() {
+        public boolean onMenuItemClick(MenuItem item) {
+            toggleDataDisabledOnBoot();
+            return true;
+        }
+    };
+    
+    private MenuItem.OnMenuItemClickListener mToggleData = new MenuItem.OnMenuItemClickListener() {
+        public boolean onMenuItemClick(MenuItem item) {
+            int state = mTelephonyManager.getDataState();
+            switch (state) {
+                case TelephonyManager.DATA_CONNECTED:
+                    phone.disableDataConnectivity();
+                    break;
+                case TelephonyManager.DATA_DISCONNECTED:
+                    phone.enableDataConnectivity();
+                    break;
+                default:
+                    // do nothing
+                    break;
+            }
+            return true;
+        }
+    };
+
+    private MenuItem.OnMenuItemClickListener mGetPdpList = new MenuItem.OnMenuItemClickListener() {
+        public boolean onMenuItemClick(MenuItem item) {
+            phone.getPdpContextList(null);
+            return true;
+        }
+    };
+
+    private MenuItem.OnMenuItemClickListener mSelectBandCallback = new MenuItem.OnMenuItemClickListener() {
+        public boolean onMenuItemClick(MenuItem item) {
+            Intent intent = new Intent();
+            intent.setClass(RadioInfo.this, BandMode.class);
+            startActivity(intent);
+            return true;
+        }
+    };
+
+    OnClickListener mPowerButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            //log("toggle radio power: currently " + (isRadioOn()?"on":"off"));
+            phone.setRadioPower(!isRadioOn());
+        }
+    };
+
+    OnClickListener mCipherButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            mCipherOn = !getCiphPref();
+            byte[] data = mOem.getCipheringData(mCipherOn);
+            
+            if (data == null)
+                return;
+
+            cipherState.setText("Setting...");
+            phone.invokeOemRilRequestRaw(data,
+                    mHandler.obtainMessage(EVENT_SET_CIPHER_DONE));
+        }
+    };
+    
+    OnClickListener mDnsCheckButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            GSMPhone gsmPhone = (GSMPhone) phone;
+            gsmPhone.disableDnsCheck(!gsmPhone.isDnsCheckDisabled());
+            updateDnsCheckState();
+        }
+    };
+    
+    OnClickListener mPingButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            updatePingState();
+        }
+    };
+
+    OnClickListener mUpdateSmscButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            updateSmscButton.setEnabled(false);
+            byte[] data = mOem.getSmscUpdateData(smsc.getText().toString());
+            if (data == null) return;
+            phone.invokeOemRilRequestRaw(data,
+                    mHandler.obtainMessage(EVENT_UPDATE_SMSC_DONE));
+        }
+    };
+
+    OnClickListener mRefreshSmscButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            refreshSmsc();
+        }
+    };
+
+    OnClickListener mQxdmButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            byte[] data = mOem.getQxdmSdlogData(
+                    !mQxdmLogEnabled,
+                    mOem.OEM_QXDM_SDLOG_DEFAULT_FILE_SIZE,
+                    mOem.OEM_QXDM_SDLOG_DEFAULT_MASK,
+                    mOem.OEM_QXDM_SDLOG_DEFAULT_MAX_INDEX);
+
+            if (data == null)
+                return;
+
+            phone.invokeOemRilRequestRaw(data,
+                    mHandler.obtainMessage(EVENT_SET_QXDMLOG_DONE));
+        }
+    };
+
+    AdapterView.OnItemSelectedListener
+            mPreferredNetworkHandler = new AdapterView.OnItemSelectedListener() {
+        public void onItemSelected(AdapterView parent, View v, int pos, long id) {
+            Message msg = mHandler.obtainMessage(EVENT_SET_PREFERRED_TYPE_DONE);
+            if (pos>=0 && pos<=2) {
+                phone.setPreferredNetworkType(pos, msg);
+            }
+        }
+
+        public void onNothingSelected(AdapterView parent) {
+        }
+    };
+
+    private String[] mPreferredNetworkLabels = {
+            "WCDMA preferred", "GSM only", "WCDMA only", "Unknown"};
+}
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/SdCardIntentReceiver.java b/src/com/android/settings/SdCardIntentReceiver.java
new file mode 100644
index 0000000..9648ec1
--- /dev/null
+++ b/src/com/android/settings/SdCardIntentReceiver.java
@@ -0,0 +1,70 @@
+/*
+ * 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.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * 
+ */
+public class SdCardIntentReceiver extends BroadcastReceiver {
+
+    private static final int SDCARD_STATUS = 1;
+    private static final String TAG = "SdCardIntentReceiver";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        NotificationManager nm = (NotificationManager) context
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+        String action = intent.getAction();
+        if (Config.LOGD) Log.d(TAG, "onReceiveIntent " + action);
+
+        if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
+            nm.cancel(SDCARD_STATUS);
+
+            Intent statusIntent = new Intent(Intent.ACTION_MAIN, null);
+            statusIntent.setClass(context, SdCardSettings.class);
+            nm.notify(SDCARD_STATUS, new Notification(context,
+                    android.R.drawable.stat_notify_sdcard,
+                    null,
+                    System.currentTimeMillis(),
+                    context.getText(R.string.sdcard_setting),
+                    null,
+                    statusIntent));
+        } else if (action.equals(Intent.ACTION_MEDIA_REMOVED)) {
+            nm.cancel(SDCARD_STATUS);
+        } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
+            nm.cancel(SDCARD_STATUS);
+
+            Intent statusIntent = new Intent(Intent.ACTION_MAIN, null);
+            statusIntent.setClass(context, SdCardSettings.class);
+            nm.notify(SDCARD_STATUS, new Notification(context,
+                    android.R.drawable.stat_notify_sdcard_usb,
+                    null,
+                    System.currentTimeMillis(),
+                    "SD Card",
+                    null,
+                    statusIntent));
+        }
+    }
+}
diff --git a/src/com/android/settings/SdCardSettings.java b/src/com/android/settings/SdCardSettings.java
new file mode 100644
index 0000000..b6935a2
--- /dev/null
+++ b/src/com/android/settings/SdCardSettings.java
@@ -0,0 +1,232 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Environment;
+import android.os.IMountService;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import java.io.File;
+
+
+public class SdCardSettings extends Activity
+{
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.sdcard_settings_screen);
+
+        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
+
+        mRemovedLayout = findViewById(R.id.removed);
+        mMountedLayout = findViewById(R.id.mounted);
+        mUnmountedLayout = findViewById(R.id.unmounted);
+        mScanningLayout = findViewById(R.id.scanning);
+        mSharedLayout = findViewById(R.id.shared);
+        mBadRemovalLayout = findViewById(R.id.bad_removal);
+        mReadOnlyStatus = findViewById(R.id.read_only);
+
+        mMassStorage = (CheckBox)findViewById(R.id.mass_storage);
+        mMassStorage.setOnClickListener(mMassStorageListener);
+
+        Button unmountButton = (Button)findViewById(R.id.sdcard_unmount);
+        unmountButton.setOnClickListener(mUnmountButtonHandler);
+
+        Button formatButton = (Button)findViewById(R.id.sdcard_format);
+        formatButton.setOnClickListener(mFormatButtonHandler);
+
+        mTotalSize = (TextView)findViewById(R.id.total);
+        mUsedSize = (TextView)findViewById(R.id.used);
+        mAvailableSize = (TextView)findViewById(R.id.available);
+
+        // install an intent filter to receive SD card related events.
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_REMOVED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SHARED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
+        intentFilter.addAction(Intent.ACTION_MEDIA_NOFS);
+        intentFilter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        intentFilter.addDataScheme("file");
+        registerReceiver(mReceiver, intentFilter);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        update();
+    }
+
+    private void setLayout(View layout) {
+        mRemovedLayout.setVisibility(layout == mRemovedLayout ? View.VISIBLE : View.GONE);
+        mMountedLayout.setVisibility(layout == mMountedLayout ? View.VISIBLE : View.GONE);
+        mUnmountedLayout.setVisibility(layout == mUnmountedLayout ? View.VISIBLE : View.GONE);
+        mScanningLayout.setVisibility(layout == mScanningLayout ? View.VISIBLE : View.GONE);
+        mSharedLayout.setVisibility(layout == mSharedLayout ? View.VISIBLE : View.GONE);
+        mBadRemovalLayout.setVisibility(layout == mBadRemovalLayout ? View.VISIBLE : View.GONE);
+    }
+
+    private void update() {
+        try {
+            mMassStorage.setChecked(mMountService.getMassStorageEnabled());
+        } catch (RemoteException ex) {
+        }
+
+        String scanVolume = null; // this no longer exists: SystemProperties.get(MediaScanner.CURRENT_VOLUME_PROPERTY, "");
+        boolean scanning = "external".equals(scanVolume);
+
+        if (scanning) {
+            setLayout(mScanningLayout);
+        } else {
+            String status = Environment.getExternalStorageState();
+            boolean readOnly = false;
+
+            if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
+                status = Environment.MEDIA_MOUNTED;
+                readOnly = true;
+            }
+
+            if (status.equals(Environment.MEDIA_MOUNTED)) {
+                try {
+                    File path = Environment.getExternalStorageDirectory();
+                    StatFs stat = new StatFs(path.getPath());
+                    long blockSize = stat.getBlockSize();
+                    long totalBlocks = stat.getBlockCount();
+                    long availableBlocks = stat.getAvailableBlocks();
+
+                    mTotalSize.setText(formatSize(totalBlocks * blockSize));
+                    mUsedSize.setText(formatSize((totalBlocks - availableBlocks) * blockSize));
+                    mAvailableSize.setText(formatSize(availableBlocks * blockSize));
+                } catch (IllegalArgumentException e) {
+                    // this can occur if the SD card is removed, but we haven't received the
+                    // ACTION_MEDIA_REMOVED Intent yet.
+                    status = Environment.MEDIA_REMOVED;
+                }
+
+                mReadOnlyStatus.setVisibility(readOnly ? View.VISIBLE : View.GONE);
+                setLayout(mMountedLayout);
+            } else if (status.equals(Environment.MEDIA_UNMOUNTED)) {
+                setLayout(mUnmountedLayout);
+            } else if (status.equals(Environment.MEDIA_REMOVED)) {
+                setLayout(mRemovedLayout);
+            } else if (status.equals(Environment.MEDIA_SHARED)) {
+                setLayout(mSharedLayout);
+            } else if (status.equals(Environment.MEDIA_BAD_REMOVAL)) {
+                setLayout(mBadRemovalLayout);
+            }
+        }
+    }
+
+    private String formatSize(long size) {
+        String suffix = null;
+
+        // add K or M suffix if size is greater than 1K or 1M
+        if (size >= 1024) {
+            suffix = "K";
+            size /= 1024;
+            if (size >= 1024) {
+                suffix = "M";
+                size /= 1024;
+            }
+        }
+
+        StringBuilder resultBuffer = new StringBuilder(Long.toString(size));
+
+        int commaOffset = resultBuffer.length() - 3;
+        while (commaOffset > 0) {
+            resultBuffer.insert(commaOffset, ',');
+            commaOffset -= 3;
+        }
+
+        if (suffix != null)
+            resultBuffer.append(suffix);
+        return resultBuffer.toString();
+    }
+
+    OnClickListener mMassStorageListener = new OnClickListener() {
+        public void onClick(View v) {
+            try {
+                mMountService.setMassStorageEnabled(mMassStorage.isChecked());
+            } catch (RemoteException ex) {
+            }
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            update();
+        }
+    };
+
+    OnClickListener mUnmountButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            try {
+                mMountService.unmountMedia(Environment.getExternalStorageDirectory().toString());
+            } catch (RemoteException ex) {
+            }
+        }
+    };
+
+    OnClickListener mFormatButtonHandler = new OnClickListener() {
+        public void onClick(View v) {
+            try {
+                mMountService.formatMedia(Environment.getExternalStorageDirectory().toString());
+            } catch (RemoteException ex) {
+            }
+        }
+    };
+
+
+    private int         mStatus;
+    private IMountService   mMountService;
+
+    private CheckBox    mMassStorage;
+
+    private TextView    mTotalSize;
+    private TextView    mUsedSize;
+    private TextView    mAvailableSize;
+
+    private View        mRemovedLayout;
+    private View        mMountedLayout;
+    private View        mUnmountedLayout;
+    private View        mScanningLayout;
+    private View        mSharedLayout;
+    private View        mBadRemovalLayout;
+    private View        mReadOnlyStatus;
+}
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
new file mode 100644
index 0000000..cd26492
--- /dev/null
+++ b/src/com/android/settings/SecuritySettings.java
@@ -0,0 +1,304 @@
+/*
+ * 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.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.util.Config;
+import android.util.Log;
+
+import com.android.internal.widget.LockPatternUtils;
+
+/**
+ * Gesture lock pattern settings.
+ */
+public class SecuritySettings extends PreferenceActivity
+    implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+    // Lock Settings
+    
+    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_THEN_DISABLE_REQUEST_CODE = 55;
+    private static final int CONFIRM_PATTERN_THEN_ENABLE_REQUEST_CODE = 56;
+
+    private LockPatternUtils mLockPatternUtils;
+    private CheckBoxPreference mLockEnabled;
+    private CheckBoxPreference mVisiblePattern;
+    private CheckBoxPreference mTactileFeedback;
+    private Preference mChoosePattern;
+
+    private CheckBoxPreference mShowPassword;
+    
+    // Location Settings
+    
+    private static final String LOCATION_NETWORK = "location_network";
+    private static final String LOCATION_GPS = "location_gps";
+
+    private CheckBoxPreference mNetwork;
+    private CheckBoxPreference mGps;
+    private LocationManager mLocationManager;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.security_settings);
+
+        mLockPatternUtils = new LockPatternUtils(getContentResolver());
+
+        createPreferenceHierarchy();
+        
+        // Get the available location providers
+        mLocationManager = (LocationManager)
+            getSystemService(Context.LOCATION_SERVICE);
+
+        mNetwork = (CheckBoxPreference) getPreferenceScreen().findPreference(LOCATION_NETWORK);
+        mGps = (CheckBoxPreference) getPreferenceScreen().findPreference(LOCATION_GPS);
+        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+        updateToggles();
+    }
+
+    private PreferenceScreen createPreferenceHierarchy() {
+        // Root
+        PreferenceScreen root = this.getPreferenceScreen();
+
+        // Inline preferences
+        PreferenceCategory inlinePrefCat = new PreferenceCategory(this);
+        inlinePrefCat.setTitle(R.string.lock_settings_title);
+        root.addPreference(inlinePrefCat);
+
+        // autolock toggle
+        mLockEnabled = new LockEnabledPref(this);
+        mLockEnabled.setTitle(R.string.lockpattern_settings_enable_title);
+        mLockEnabled.setSummary(R.string.lockpattern_settings_enable_summary);
+        mLockEnabled.setKey(KEY_LOCK_ENABLED);
+        inlinePrefCat.addPreference(mLockEnabled);
+
+        // visible pattern
+        mVisiblePattern = new CheckBoxPreference(this);
+        mVisiblePattern.setKey(KEY_VISIBLE_PATTERN);
+        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",
+                    "com.android.settings.ChooseLockPatternTutorial");
+        mChoosePattern = getPreferenceManager().createPreferenceScreen(this);
+        mChoosePattern.setIntent(intent);
+        inlinePrefCat.addPreference(mChoosePattern);
+        
+        PreferenceScreen simLockPreferences = getPreferenceManager()
+                .createPreferenceScreen(this);
+        simLockPreferences.setTitle(R.string.sim_lock_settings_category);
+        // Intent to launch SIM lock settings
+        intent = new Intent();
+        intent.setClassName("com.android.settings", "com.android.settings.SimLockSettings");
+        simLockPreferences.setIntent(intent);
+        
+        PreferenceCategory simLockCat = new PreferenceCategory(this);
+        simLockCat.setTitle(R.string.sim_lock_settings_title);
+        root.addPreference(simLockCat);
+        simLockCat.addPreference(simLockPreferences);
+
+        // Passwords
+        PreferenceCategory passwordsCat = new PreferenceCategory(this);
+        passwordsCat.setTitle(R.string.security_passwords_title);
+        root.addPreference(passwordsCat);
+        
+        CheckBoxPreference showPassword = mShowPassword = new CheckBoxPreference(this);
+        showPassword.setKey("show_password");
+        showPassword.setTitle(R.string.show_password);
+        showPassword.setSummary(R.string.show_password_summary);
+        showPassword.setPersistent(false);
+        passwordsCat.addPreference(showPassword);
+        
+        return root;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        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 :
+                R.string.lockpattern_settings_choose_lock_pattern;
+        mChoosePattern.setTitle(chooseStringRes);
+        
+        mShowPassword
+                .setChecked(Settings.System.getInt(getContentResolver(),
+                Settings.System.TEXT_SHOW_PASSWORD, 1) != 0);
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+            Preference preference) {
+        final String key = preference.getKey();
+
+        if (KEY_LOCK_ENABLED.equals(key)) {
+            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);
+        }
+        
+        return false;
+    }
+
+    /*
+     * Creates toggles for each available location provider
+     */
+    private void updateToggles() {
+        String providers = getAllowedProviders();
+        mNetwork.setChecked(providers.contains(LocationManager.NETWORK_PROVIDER));
+        mGps.setChecked(providers.contains(LocationManager.GPS_PROVIDER));
+    }
+
+    private void updateProviders() {
+        String preferredProviders = "";
+        if (mNetwork.isChecked()) {
+            preferredProviders += LocationManager.NETWORK_PROVIDER;
+        }
+        if (mGps.isChecked()) {
+            preferredProviders += "," + LocationManager.GPS_PROVIDER;
+        }
+        setProviders(preferredProviders);
+    }
+
+    private void setProviders(String 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);
+        }
+        // Inform the location manager about the changes
+        mLocationManager.updateProviders();
+    }
+
+    /**
+     * @return string containing a list of providers that have been enabled for use
+     */
+    private String getAllowedProviders() {
+        String allowedProviders =
+            Settings.Secure.getString(getContentResolver(),
+                Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
+        if (allowedProviders == null) {
+            allowedProviders = "";
+        }
+        return allowedProviders;
+    }
+
+    public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
+        if (LOCATION_NETWORK.equals(key) || LOCATION_GPS.equals(key)) {
+            updateProviders();
+        }
+    }
+
+    private boolean isToggled(Preference pref) {
+        return ((CheckBoxPreference) pref).isChecked();
+    }
+
+
+    /**
+     * For the user to disable keyguard, we first make them verify their
+     * existing pattern.
+     */
+    private class LockEnabledPref extends CheckBoxPreference {
+
+        public LockEnabledPref(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onClick() {
+            if (mLockPatternUtils.savedPatternExists()) {
+                if (isChecked()) {
+                    confirmPatternThenDisable();
+                } else {
+                    confirmPatternThenEnable();
+                }
+            } else {
+                super.onClick();
+            }
+        }
+    }
+
+    private void confirmPatternThenEnable() {
+        final Intent intent = new Intent();
+        intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern");
+        startActivityForResult(intent, CONFIRM_PATTERN_THEN_ENABLE_REQUEST_CODE);
+    }
+
+    /**
+     * Launch screen to confirm the existing lock pattern.
+     * @see #onActivityResult(int, int, android.content.Intent)
+     */
+    private void confirmPatternThenDisable() {
+        final Intent intent = new Intent();
+        intent.setClassName("com.android.settings", "com.android.settings.ConfirmLockPattern");
+        startActivityForResult(intent, CONFIRM_PATTERN_THEN_DISABLE_REQUEST_CODE);
+    }
+
+    /**
+     * @see #confirmPatternThenDisable
+     */
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode,
+            Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        final boolean resultOk = resultCode == Activity.RESULT_OK;
+
+        if ((requestCode == CONFIRM_PATTERN_THEN_DISABLE_REQUEST_CODE) && resultOk) {
+            mLockPatternUtils.setLockPatternEnabled(false);
+        } else if ((requestCode == CONFIRM_PATTERN_THEN_ENABLE_REQUEST_CODE) && resultOk) {
+            mLockPatternUtils.setLockPatternEnabled(true);
+        }
+    }
+}
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
new file mode 100644
index 0000000..0c4545e
--- /dev/null
+++ b/src/com/android/settings/Settings.java
@@ -0,0 +1,46 @@
+/*
+ * 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.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.provider.Settings.System;
+
+public class Settings extends PreferenceActivity {
+
+    private static final String KEY_PARENT = "parent";
+    private static final String KEY_CALL_SETTINGS = "call_settings";
+    private static final String KEY_SYNC_SETTINGS = "sync_settings";
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        addPreferencesFromResource(R.xml.settings);
+        
+        PreferenceGroup parent = (PreferenceGroup) findPreference(KEY_PARENT);
+        Utils.updatePreferenceToSpecificActivityOrRemove(this, parent, KEY_SYNC_SETTINGS, 0);
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        findPreference(KEY_CALL_SETTINGS).setEnabled(!AirplaneModeEnabler.isAirplaneModeOn(this));
+    }
+
+}
diff --git a/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java
new file mode 100644
index 0000000..0b809e1
--- /dev/null
+++ b/src/com/android/settings/SettingsLicenseActivity.java
@@ -0,0 +1,123 @@
+/*
+ * 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.os.Bundle;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Toast;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.zip.GZIPInputStream;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * The "dialog" that shows from "License" in the Settings app.
+ */
+public class SettingsLicenseActivity extends AlertActivity {
+
+    private static final String TAG = "SettingsLicenseActivity";
+    private static final boolean LOGV = false || Config.LOGV;
+
+    private static final String DEFAULT_LICENSE_PATH = "/system/etc/NOTICE.html.gz";
+    private static final String PROPERTY_LICENSE_PATH = "ro.config.license_path";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String fileName = SystemProperties.get(PROPERTY_LICENSE_PATH, DEFAULT_LICENSE_PATH);
+        if (TextUtils.isEmpty(fileName)) {
+            Log.e(TAG, "The system property for the license file is empty.");
+            showErrorAndFinish();
+            return;
+        }
+
+        InputStreamReader inputReader = null;
+        StringBuilder data = null;
+        try {
+            data = new StringBuilder(2048);
+            char tmp[] = new char[2048];
+            int numRead;
+            if (fileName.endsWith(".gz")) {
+                inputReader = new InputStreamReader(
+                    new GZIPInputStream(new FileInputStream(fileName)));
+            } else {
+                inputReader = new FileReader(fileName);
+            }
+            while ((numRead = inputReader.read(tmp)) >= 0) {
+                data.append(tmp, 0, numRead);
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "License HTML file not found at " + fileName, e);
+            showErrorAndFinish();
+            return;
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading license HTML file at " + fileName, e);
+            showErrorAndFinish();
+            return;
+        } finally {
+            try {
+                if (inputReader != null) {
+                    inputReader.close();
+                }
+            } catch (IOException e) {
+            }
+        }
+
+        if (TextUtils.isEmpty(data)) {
+            Log.e(TAG, "License HTML is empty (from " + fileName + ")");
+            showErrorAndFinish();
+            return;
+        }
+
+        WebView webView = new WebView(this);
+
+        // Begin the loading.  This will be done in a separate thread in WebView.
+        webView.loadDataWithBaseURL(null, data.toString(), "text/html", "utf-8", null);
+        webView.setWebViewClient(new WebViewClient() {
+            @Override
+            public void onPageFinished(WebView view, String url) {
+                // Change from 'Loading...' to the real title
+                mAlert.setTitle(getString(R.string.settings_license_activity_title));
+            }
+        });
+
+        final AlertController.AlertParams p = mAlertParams;
+        p.mTitle = getString(R.string.settings_license_activity_loading);
+        p.mView = webView;
+        p.mForceInverseBackground = true;
+        setupAlert();
+    }
+
+    private void showErrorAndFinish() {
+        Toast.makeText(this, R.string.settings_license_activity_unavailable, Toast.LENGTH_LONG)
+                .show();
+        finish();
+    }
+
+}
diff --git a/src/com/android/settings/SimLockSettings.java b/src/com/android/settings/SimLockSettings.java
new file mode 100644
index 0000000..286e3d6
--- /dev/null
+++ b/src/com/android/settings/SimLockSettings.java
@@ -0,0 +1,321 @@
+/*
+ * 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.content.res.Resources;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.CheckBoxPreference;
+import android.preference.PreferenceScreen;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import android.widget.Toast;
+
+/**
+ * Implements the preference screen to enable/disable SIM lock and
+ * also the dialogs to change the SIM PIN. In the former case, enabling/disabling
+ * the SIM lock will prompt the user for the current PIN.
+ * In the Change PIN case, it prompts the user for old pin, new pin and new pin
+ * again before attempting to change it. Calls the SimCard interface to execute
+ * these operations.
+ *
+ */
+public class SimLockSettings extends PreferenceActivity 
+        implements EditPinPreference.OnPinEnteredListener {
+
+    private static final int OFF_MODE = 0;
+    // State when enabling/disabling SIM lock
+    private static final int SIM_LOCK_MODE = 1;
+    // State when entering the old pin
+    private static final int SIM_OLD_MODE = 2;
+    // State when entering the new pin - first time
+    private static final int SIM_NEW_MODE = 3;
+    // State when entering the new pin - second time
+    private static final int SIM_REENTER_MODE = 4;
+    
+    // Keys in xml file
+    private static final String PIN_DIALOG = "sim_pin";
+    private static final String PIN_TOGGLE = "sim_toggle";
+    // Keys in icicle
+    private static final String DIALOG_STATE = "dialogState";
+    private static final String DIALOG_PIN = "dialogPin";
+    private static final String DIALOG_ERROR = "dialogError";
+    private static final String ENABLE_TO_STATE = "enableState";
+    
+    private static final int MIN_PIN_LENGTH = 4;
+    private static final int MAX_PIN_LENGTH = 8;
+    // Which dialog to show next when popped up
+    private int mDialogState = OFF_MODE;
+    
+    private String mPin;
+    private String mOldPin;
+    private String mNewPin;
+    private String mError;
+    // Are we trying to enable or disable SIM lock?
+    private boolean mToState;
+    
+    private Phone mPhone;
+    
+    private EditPinPreference mPinDialog;
+    private CheckBoxPreference mPinToggle;
+    
+    private Resources mRes;
+
+    // For async handler to identify request type
+    private static final int ENABLE_SIM_PIN_COMPLETE = 100;
+    private static final int CHANGE_SIM_PIN_COMPLETE = 101;
+
+    // For replies from SimCard interface
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+            switch (msg.what) {
+                case ENABLE_SIM_PIN_COMPLETE:
+                    simLockChanged(ar.exception == null);
+                    break;
+                case CHANGE_SIM_PIN_COMPLETE:
+                    simPinChanged(ar.exception == null);
+                    break;
+            }
+
+            return;
+        }
+    };
+    
+    // For top-level settings screen to query
+    static boolean isSimLockEnabled() {
+        return PhoneFactory.getDefaultPhone().getSimCard().getSimLockEnabled();
+    }
+    
+    static String getSummary(Context context) {
+        Resources res = context.getResources();
+        String summary = isSimLockEnabled() 
+                ? res.getString(R.string.sim_lock_on) 
+                : res.getString(R.string.sim_lock_off);
+        return summary;
+    }
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    
+        addPreferencesFromResource(R.xml.sim_lock_settings);
+        
+        mPinDialog = (EditPinPreference) findPreference(PIN_DIALOG);
+        mPinToggle = (CheckBoxPreference) findPreference(PIN_TOGGLE);
+        if (savedInstanceState != null && savedInstanceState.containsKey(DIALOG_STATE)) {
+            mDialogState = savedInstanceState.getInt(DIALOG_STATE);
+            mPin = savedInstanceState.getString(DIALOG_PIN);
+            mError = savedInstanceState.getString(DIALOG_ERROR);
+            mToState = savedInstanceState.getBoolean(ENABLE_TO_STATE);
+        }
+
+        mPinDialog.setOnPinEnteredListener(this);
+        
+        // Don't need any changes to be remembered
+        getPreferenceScreen().setPersistent(false);
+        
+        mPhone = PhoneFactory.getDefaultPhone();
+        mRes = getResources();
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        mPinToggle.setChecked(mPhone.getSimCard().getSimLockEnabled());
+        
+        if (mDialogState != OFF_MODE) {
+            showPinDialog();
+        } else {
+            // Prep for standard click on "Change PIN"
+            resetDialogState();
+        }
+    }
+    
+    @Override
+    protected void onSaveInstanceState(Bundle out) {
+        // Need to store this state for slider open/close
+        // There is one case where the dialog is popped up by the preference
+        // framework. In that case, let the preference framework store the
+        // dialog state. In other cases, where this activity manually launches
+        // the dialog, store the state of the dialog.
+        if (mPinDialog.isDialogOpen()) {
+            out.putInt(DIALOG_STATE, mDialogState);
+            out.putString(DIALOG_PIN, mPinDialog.getEditText().getText().toString());
+            out.putString(DIALOG_ERROR, mError);
+            out.putBoolean(ENABLE_TO_STATE, mToState);
+        } else {
+            super.onSaveInstanceState(out);
+        }
+    }
+
+    private void showPinDialog() {
+        if (mDialogState == OFF_MODE) {
+            return;
+        }
+        setDialogValues();
+        
+        mPinDialog.showPinDialog();
+    }
+    
+    private void setDialogValues() {
+        mPinDialog.setText(mPin);
+        String message = "";
+        switch (mDialogState) {
+            case SIM_LOCK_MODE:
+                message = mRes.getString(R.string.sim_enter_pin);
+                mPinDialog.setDialogTitle(mToState 
+                        ? mRes.getString(R.string.sim_enable_sim_lock)
+                        : mRes.getString(R.string.sim_disable_sim_lock));
+                break;
+            case SIM_OLD_MODE:
+                message = mRes.getString(R.string.sim_enter_old);
+                mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
+                break;
+            case SIM_NEW_MODE:
+                message = mRes.getString(R.string.sim_enter_new);
+                mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
+                break;
+            case SIM_REENTER_MODE:
+                message = mRes.getString(R.string.sim_reenter_new);
+                mPinDialog.setDialogTitle(mRes.getString(R.string.sim_change_pin));
+                break;
+        }
+        if (mError != null) {
+            message = mError + "\n" + message;
+            mError = null;
+        }
+        mPinDialog.setDialogMessage(message);
+    }
+
+    public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
+        if (!positiveResult) {
+            resetDialogState();
+            return;
+        }
+        
+        mPin = preference.getText();
+        if (!reasonablePin(mPin)) {
+            // inject error message and display dialog again
+            mError = mRes.getString(R.string.sim_bad_pin);
+            showPinDialog();
+            return;
+        }
+        switch (mDialogState) {
+            case SIM_LOCK_MODE:
+                tryChangeSimLockState();
+                break;
+            case SIM_OLD_MODE:
+                mOldPin = mPin;
+                mDialogState = SIM_NEW_MODE;
+                mError = null;
+                mPin = null;
+                showPinDialog();
+                break;
+            case SIM_NEW_MODE:
+                mNewPin = mPin;
+                mDialogState = SIM_REENTER_MODE;
+                mPin = null;
+                showPinDialog();
+                break;
+            case SIM_REENTER_MODE:
+                if (!mPin.equals(mNewPin)) {
+                    mError = mRes.getString(R.string.sim_pins_dont_match);
+                    mDialogState = SIM_NEW_MODE;
+                    mPin = null;
+                    showPinDialog();
+                } else {
+                    mError = null;
+                    tryChangePin();
+                }
+                break;
+        }
+    }
+    
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        if (preference == mPinToggle) {
+            // Get the new, preferred state
+            mToState = mPinToggle.isChecked();
+            // Flip it back and pop up pin dialog  
+            mPinToggle.setChecked(!mToState);  
+            mDialogState = SIM_LOCK_MODE;
+            showPinDialog();
+        }
+        return true;
+    }
+    
+    private void tryChangeSimLockState() {
+        // Try to change sim lock. If it succeeds, toggle the lock state and 
+        // reset dialog state. Else inject error message and show dialog again.
+        Message callback = Message.obtain(mHandler, ENABLE_SIM_PIN_COMPLETE);
+        mPhone.getSimCard().setSimLockEnabled(mToState, mPin, callback);
+
+    }
+    
+    private void simLockChanged(boolean success) {
+        if (success) {
+            mPinToggle.setChecked(mToState);
+        } else {
+            // TODO: I18N
+            Toast.makeText(this, mRes.getString(R.string.sim_lock_failed), Toast.LENGTH_SHORT)
+                    .show();
+        }
+        resetDialogState();
+    }
+
+    private void simPinChanged(boolean success) {
+        if (!success) {
+         // TODO: I18N
+            Toast.makeText(this, mRes.getString(R.string.sim_change_failed), 
+                    Toast.LENGTH_SHORT)
+                    .show();
+        } else {
+            Toast.makeText(this, mRes.getString(R.string.sim_change_succeeded), 
+                    Toast.LENGTH_SHORT)
+                    .show();
+
+        }
+        resetDialogState();
+    }
+
+    private void tryChangePin() {
+        Message callback = Message.obtain(mHandler, CHANGE_SIM_PIN_COMPLETE);
+        mPhone.getSimCard().changeSimLockPassword(mOldPin,
+                mNewPin, callback);
+    }
+    
+    private boolean reasonablePin(String pin) {
+        if (pin == null || pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+ 
+    private void resetDialogState() {
+        mError = null;
+        mDialogState = SIM_OLD_MODE; // Default for when Change PIN is clicked
+        mPin = "";
+        setDialogValues();
+    }
+}
diff --git a/src/com/android/settings/SoundAndDisplaySettings.java b/src/com/android/settings/SoundAndDisplaySettings.java
new file mode 100644
index 0000000..53912e3
--- /dev/null
+++ b/src/com/android/settings/SoundAndDisplaySettings.java
@@ -0,0 +1,245 @@
+/*
+ * 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 static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.IMountService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.preference.CheckBoxPreference;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.IWindowManager;
+
+public class SoundAndDisplaySettings extends PreferenceActivity implements
+        Preference.OnPreferenceChangeListener {
+    private static final String TAG = "SoundAndDisplaysSettings";
+
+    /** If there is no setting in the provider, use this. */
+    private static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000;
+    
+    private static final String KEY_SILENT = "silent";
+    private static final String KEY_VIBRATE = "vibrate";
+    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 static final String KEY_PLAY_MEDIA_NOTIFICATION_SOUNDS = "play_media_notification_sounds";
+    
+    private CheckBoxPreference mSilent;
+
+    private CheckBoxPreference mPlayMediaNotificationSounds;
+
+    private IMountService mMountService = null;
+
+    /*
+     * If we are currently in one of the silent modes (the ringer mode is set to either
+     * "silent mode" or "vibrate mode"), then toggling the "Phone vibrate"
+     * preference will switch between "silent mode" and "vibrate mode".
+     * Otherwise, it will adjust the normal ringer mode's ring or ring+vibrate
+     * setting.
+     */
+    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) {
+            updateState(false);
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ContentResolver resolver = getContentResolver();
+        
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+
+        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
+        
+        addPreferencesFromResource(R.xml.sound_and_display_settings);
+        
+        mSilent = (CheckBoxPreference) findPreference(KEY_SILENT);
+        mPlayMediaNotificationSounds = (CheckBoxPreference) findPreference(KEY_PLAY_MEDIA_NOTIFICATION_SOUNDS);
+
+        mVibrate = (CheckBoxPreference) findPreference(KEY_VIBRATE);
+        mDtmfTone = (CheckBoxPreference) findPreference(KEY_DTMF_TONE);
+        mDtmfTone.setPersistent(false);
+        mDtmfTone.setChecked(Settings.System.getInt(resolver,
+                Settings.System.DTMF_TONE_WHEN_DIALING, 1) != 0);
+        mSoundEffects = (CheckBoxPreference) findPreference(KEY_SOUND_EFFECTS);
+        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(
+                resolver, SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE)));
+        screenTimeoutPreference.setOnPreferenceChangeListener(this);
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        updateState(true);
+        
+        IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
+        registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        unregisterReceiver(mReceiver);
+    }
+
+    private void updateState(boolean force) {
+        final int ringerMode = mAudioManager.getRingerMode();
+        final boolean silentOrVibrateMode =
+                ringerMode != AudioManager.RINGER_MODE_NORMAL;
+        
+        if (silentOrVibrateMode != mSilent.isChecked() || force) {
+            mSilent.setChecked(silentOrVibrateMode);
+        }
+
+        try {
+            mPlayMediaNotificationSounds.setChecked(mMountService.getPlayNotificationSounds());
+        } catch (RemoteException e) {
+        }
+       
+        boolean vibrateSetting;
+        if (silentOrVibrateMode) {
+            vibrateSetting = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
+        } else {
+            vibrateSetting = mAudioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER)
+                    == AudioManager.VIBRATE_SETTING_ON;            
+        }
+        if (vibrateSetting != mVibrate.isChecked() || force) {
+            mVibrate.setChecked(vibrateSetting);
+        }
+        
+        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
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+
+        if (preference == mSilent) {
+            final boolean silent = mSilent.isChecked();
+            mAudioManager.setRingerMode(silent ? AudioManager.RINGER_MODE_SILENT
+                    : AudioManager.RINGER_MODE_NORMAL);
+            updateState(false);
+            
+        } else if (preference == mPlayMediaNotificationSounds) {
+            try {
+                mMountService.setPlayNotificationSounds(mPlayMediaNotificationSounds.isChecked());
+            } catch (RemoteException e) {
+            }
+        } else if (preference == mVibrate) {
+            final boolean vibrate = mVibrate.isChecked();
+            final boolean silent = mSilent.isChecked();
+            
+            if (silent) {
+                mAudioManager.setRingerMode(vibrate ? AudioManager.RINGER_MODE_VIBRATE :
+                    AudioManager.RINGER_MODE_SILENT);                
+            } else {
+                mAudioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER,
+                        vibrate ? AudioManager.VIBRATE_SETTING_ON
+                                : AudioManager.VIBRATE_SETTING_OFF);
+            }
+            
+        } else if (preference == mDtmfTone) {
+            Settings.System.putInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING,
+                    mDtmfTone.isChecked() ? 1 : 0);
+            
+        } else if (preference == mSoundEffects) {
+            if (mSoundEffects.isChecked()) {
+                mAudioManager.loadSoundEffects();
+            } else {
+                mAudioManager.unloadSoundEffects();
+            }
+            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;
+    }
+
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (KEY_SCREEN_TIMEOUT.equals(preference.getKey())) {
+            int value = Integer.parseInt((String) objValue);
+            try {
+                Settings.System.putInt(getContentResolver(), 
+                        SCREEN_OFF_TIMEOUT, value);
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "could not persist screen timeout setting", e);
+            }
+        }
+        
+        return true;
+    }
+
+}
diff --git a/src/com/android/settings/TestingSettings.java b/src/com/android/settings/TestingSettings.java
new file mode 100644
index 0000000..3994560
--- /dev/null
+++ b/src/com/android/settings/TestingSettings.java
@@ -0,0 +1,31 @@
+/*
+ * 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.os.Bundle;
+import android.preference.PreferenceActivity;
+
+public class TestingSettings extends PreferenceActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        addPreferencesFromResource(R.xml.testing_settings);
+    }
+
+}
diff --git a/src/com/android/settings/TestingSettingsBroadcastReceiver.java b/src/com/android/settings/TestingSettingsBroadcastReceiver.java
new file mode 100644
index 0000000..c6cd7e1
--- /dev/null
+++ b/src/com/android/settings/TestingSettingsBroadcastReceiver.java
@@ -0,0 +1,28 @@
+package com.android.settings;
+
+import android.provider.Telephony;
+import static android.provider.Telephony.Intents.SECRET_CODE_ACTION;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.util.Config;
+import android.util.Log;
+import android.view.KeyEvent;
+
+
+public class TestingSettingsBroadcastReceiver extends BroadcastReceiver {
+  
+    public TestingSettingsBroadcastReceiver() {
+    }
+    
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals(SECRET_CODE_ACTION)) {
+            Intent i = new Intent(Intent.ACTION_MAIN);
+            i.setClass(context, TestingSettings.class);
+            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(i);
+        }
+    }
+}
diff --git a/src/com/android/settings/UsageStats.java b/src/com/android/settings/UsageStats.java
new file mode 100755
index 0000000..89caa54
--- /dev/null
+++ b/src/com/android/settings/UsageStats.java
@@ -0,0 +1,249 @@
+
+
+/**
+ * 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 com.android.internal.app.IUsageStats;
+import com.android.settings.R;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import com.android.internal.os.PkgUsageStats;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+/**
+ * Activity to display package usage statistics.
+ */
+public class UsageStats extends Activity implements OnItemSelectedListener {
+    private static final String TAG="UsageStatsActivity";
+    private static final boolean localLOGV = true;
+    private Spinner mTypeSpinner;
+    private ListView mListView;
+    private IUsageStats mUsageStatsService;
+    private LayoutInflater mInflater;
+    private UsageStatsAdapter mAdapter;
+    private PackageManager mPm;
+    
+    public static class AppNameComparator implements Comparator<PkgUsageStats> {
+        Map<String, CharSequence> mAppLabelList;
+        AppNameComparator(Map<String, CharSequence> appList) {
+            mAppLabelList = appList;
+        }
+        public final int compare(PkgUsageStats a, PkgUsageStats b) {
+            String alabel = mAppLabelList.get(a.packageName).toString();
+            String blabel = mAppLabelList.get(b.packageName).toString();
+            return alabel.compareTo(blabel);
+        }
+    }
+    
+    public static class LaunchCountComparator implements Comparator<PkgUsageStats> {
+        public final int compare(PkgUsageStats a, PkgUsageStats b) {
+            // return by descending order
+            return b.launchCount - a.launchCount;
+        }
+    }
+    
+    public static class UsageTimeComparator implements Comparator<PkgUsageStats> {
+        public final int compare(PkgUsageStats a, PkgUsageStats b) {
+            long ret = a.usageTime-b.usageTime;
+            if (ret == 0) {
+                return 0;
+            }
+            if (ret < 0) {
+                return 1;
+            }
+            return -1;
+        }
+    }
+    
+     // View Holder used when displaying views
+    static class AppViewHolder {
+        TextView pkgName;
+        TextView launchCount;
+        TextView usageTime;
+    }
+    
+    class UsageStatsAdapter extends BaseAdapter {
+         // Constants defining order for display order
+        private static final int _DISPLAY_ORDER_USAGE_TIME = 0;
+        private static final int _DISPLAY_ORDER_LAUNCH_COUNT = 1;
+        private static final int _DISPLAY_ORDER_APP_NAME = 2;
+        
+        private int mDisplayOrder = _DISPLAY_ORDER_USAGE_TIME;
+        private List<PkgUsageStats> mUsageStats;
+        private LaunchCountComparator mLaunchCountComparator;
+        private UsageTimeComparator mUsageTimeComparator;
+        private AppNameComparator mAppLabelComparator;
+        private HashMap<String, CharSequence> mAppLabelMap;
+        
+        UsageStatsAdapter() {
+            mUsageStats = new ArrayList<PkgUsageStats>();
+            mAppLabelMap = new HashMap<String, CharSequence>();
+            PkgUsageStats[] stats;
+            try {
+                stats = mUsageStatsService.getAllPkgUsageStats();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed initializing usage stats service");
+                return;
+            }
+           if (stats == null) {
+               return;
+           }
+           for (PkgUsageStats ps : stats) {
+               mUsageStats.add(ps);
+               // load application labels for each application
+               CharSequence label;
+               try {
+                   ApplicationInfo appInfo = mPm.getApplicationInfo(ps.packageName, 0);
+                   label = appInfo.loadLabel(mPm);
+                } catch (NameNotFoundException e) {
+                    label = ps.packageName;
+                }
+                mAppLabelMap.put(ps.packageName, label);
+           }
+           // Sort list
+           mLaunchCountComparator = new LaunchCountComparator();
+           mUsageTimeComparator = new UsageTimeComparator();
+           mAppLabelComparator = new AppNameComparator(mAppLabelMap);
+           sortList();
+        }
+        public int getCount() {
+            return mUsageStats.size();
+        }
+
+        public Object getItem(int position) {
+            return mUsageStats.get(position);
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        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.usage_stats_item, null);
+
+                // Creates a ViewHolder and store references to the two children views
+                // we want to bind data to.
+                holder = new AppViewHolder();
+                holder.pkgName = (TextView) convertView.findViewById(R.id.package_name);
+                holder.launchCount = (TextView) convertView.findViewById(R.id.launch_count);
+                holder.usageTime = (TextView) convertView.findViewById(R.id.usage_time);
+                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
+            PkgUsageStats pkgStats = mUsageStats.get(position);
+            if (pkgStats != null) {
+                CharSequence label = mAppLabelMap.get(pkgStats.packageName);
+                holder.pkgName.setText(label);
+                holder.launchCount.setText(String.valueOf(pkgStats.launchCount));
+                holder.usageTime.setText(String.valueOf(pkgStats.usageTime)+" ms");
+            } else {
+                Log.w(TAG, "No usage stats info for package:"+pkgStats.packageName);
+            }
+            return convertView;
+        }
+        
+        void sortList(int sortOrder) {
+            if (mDisplayOrder == sortOrder) {
+                // do nothing
+                return;
+            }
+            mDisplayOrder= sortOrder;
+            sortList();
+        }
+        private void sortList() {
+            if (mDisplayOrder == _DISPLAY_ORDER_USAGE_TIME) {
+                if (localLOGV) Log.i(TAG, "Sorting by usage time");
+                Collections.sort(mUsageStats, mUsageTimeComparator);
+            } else if (mDisplayOrder == _DISPLAY_ORDER_LAUNCH_COUNT) {
+                if (localLOGV) Log.i(TAG, "Sorting launch count");
+                Collections.sort(mUsageStats, mLaunchCountComparator);
+            } else if (mDisplayOrder == _DISPLAY_ORDER_APP_NAME) {
+                if (localLOGV) Log.i(TAG, "Sorting by application name");
+                Collections.sort(mUsageStats, mAppLabelComparator);
+            }
+            notifyDataSetChanged();
+        }
+    }
+
+    /** Called when the activity is first created. */
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mUsageStatsService = IUsageStats.Stub.asInterface(ServiceManager.getService("usagestats"));
+        if (mUsageStatsService == null) {
+            Log.e(TAG, "Failed to retrieve usagestats service");
+            return;
+        }
+        mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mPm = getPackageManager();
+        
+        setContentView(R.layout.usage_stats);
+        mTypeSpinner = (Spinner) findViewById(R.id.typeSpinner);
+        mTypeSpinner.setOnItemSelectedListener(this);
+        
+        mListView = (ListView) findViewById(R.id.pkg_list);
+        // Initialize the inflater
+        
+        mAdapter = new UsageStatsAdapter();
+        mListView.setAdapter(mAdapter);
+    }
+
+    public void onItemSelected(AdapterView<?> parent, View view, int position,
+            long id) {
+        mAdapter.sortList(position);
+    }
+
+    public void onNothingSelected(AdapterView<?> parent) {
+        // do nothing
+    }
+}
+
diff --git a/src/com/android/settings/UserDictionarySettings.java b/src/com/android/settings/UserDictionarySettings.java
new file mode 100644
index 0000000..aeddcf7
--- /dev/null
+++ b/src/com/android/settings/UserDictionarySettings.java
@@ -0,0 +1,271 @@
+/**
+ * Copyright (C) 2007 Google Inc.
+ *
+ * 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.app.Dialog;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.UserDictionary;
+import android.text.InputType;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AlphabetIndexer;
+import android.widget.EditText;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SectionIndexer;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.util.Locale;
+
+public class UserDictionarySettings extends ListActivity {
+
+    private static final String INSTANCE_KEY_DIALOG_EDITING_WORD = "DIALOG_EDITING_WORD";
+    private static final String INSTANCE_KEY_ADDED_WORD = "DIALOG_ADDED_WORD";
+
+    private static final String[] QUERY_PROJECTION = {
+        UserDictionary.Words._ID, UserDictionary.Words.WORD
+    };
+    
+    // Either the locale is empty (means the word is applicable to all locales)
+    // or the word equals our current locale
+    private static final String QUERY_SELECTION = UserDictionary.Words.LOCALE + "=? OR "
+            + UserDictionary.Words.LOCALE + " is null";
+
+    private static final String DELETE_SELECTION = UserDictionary.Words.WORD + "=?";
+
+    private static final String EXTRA_WORD = "word";
+    
+    private static final int CONTEXT_MENU_EDIT = Menu.FIRST;
+    private static final int CONTEXT_MENU_DELETE = Menu.FIRST + 1;
+    
+    private static final int OPTIONS_MENU_ADD = Menu.FIRST;
+
+    private static final int DIALOG_ADD_OR_EDIT = 0;
+    
+    /** The word being edited in the dialog (null means the user is adding a word). */
+    private String mDialogEditingWord;
+    
+    private Cursor mCursor;
+    
+    private boolean mAddedWordAlready;
+    private boolean mAutoReturn;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.list_content_with_empty_view);
+        
+        mCursor = createCursor();
+        setListAdapter(createAdapter());
+        
+        TextView emptyView = (TextView) findViewById(R.id.empty);
+        emptyView.setText(R.string.user_dict_settings_empty_text);
+        
+        ListView listView = getListView();
+        listView.setFastScrollEnabled(true);
+        listView.setEmptyView(emptyView);
+
+        registerForContextMenu(listView);
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (!mAddedWordAlready 
+                && getIntent().getAction().equals("com.android.settings.USER_DICTIONARY_INSERT")) {
+            String word = getIntent().getStringExtra(EXTRA_WORD);
+            mAutoReturn = true;
+            if (word != null) {
+                showAddOrEditDialog(word);
+            }
+        }
+    }
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        mDialogEditingWord = state.getString(INSTANCE_KEY_DIALOG_EDITING_WORD);
+        mAddedWordAlready = state.getBoolean(INSTANCE_KEY_ADDED_WORD, false);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString(INSTANCE_KEY_DIALOG_EDITING_WORD, mDialogEditingWord);
+        outState.putBoolean(INSTANCE_KEY_ADDED_WORD, mAddedWordAlready);
+    }
+
+    private Cursor createCursor() {
+        String currentLocale = Locale.getDefault().toString();
+        // Case-insensitive sort
+        return managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION,
+                QUERY_SELECTION, new String[] { currentLocale },
+                "UPPER(" + UserDictionary.Words.WORD + ")");
+    }
+
+    private ListAdapter createAdapter() {
+        return new MyAdapter(this,
+                android.R.layout.simple_list_item_1, mCursor,
+                new String[] { UserDictionary.Words.WORD },
+                new int[] { android.R.id.text1 });
+    }
+    
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        showAddOrEditDialog(getWord(position));
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+        if (!(menuInfo instanceof AdapterContextMenuInfo)) return;
+        
+        AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
+        menu.setHeaderTitle(getWord(adapterMenuInfo.position));
+        menu.add(0, CONTEXT_MENU_EDIT, 0, R.string.user_dict_settings_context_menu_edit_title);
+        menu.add(0, CONTEXT_MENU_DELETE, 0, R.string.user_dict_settings_context_menu_delete_title);
+    }
+    
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        ContextMenuInfo menuInfo = item.getMenuInfo();
+        if (!(menuInfo instanceof AdapterContextMenuInfo)) return false;
+        
+        AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
+        String word = getWord(adapterMenuInfo.position);
+        
+        switch (item.getItemId()) {
+            case CONTEXT_MENU_DELETE:
+                deleteWord(word);
+                return true;
+                
+            case CONTEXT_MENU_EDIT:
+                showAddOrEditDialog(word);
+                return true;
+        }
+        
+        return false;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title)
+                .setIcon(R.drawable.ic_menu_add);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        showAddOrEditDialog(null);
+        return true;
+    }
+
+    private void showAddOrEditDialog(String editingWord) {
+        mDialogEditingWord = editingWord;
+        showDialog(DIALOG_ADD_OR_EDIT);
+    }
+    
+    private String getWord(int position) {
+        mCursor.moveToPosition(position);
+        return mCursor.getString(
+                mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD));
+    }
+    
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        View content = getLayoutInflater().inflate(R.layout.dialog_edittext, null);
+        final EditText editText = (EditText) content.findViewById(R.id.edittext);
+        // No prediction in soft keyboard mode. TODO: Create a better way to disable prediction
+        editText.setInputType(InputType.TYPE_CLASS_TEXT 
+                | InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+        
+        return new AlertDialog.Builder(this)
+                .setTitle(R.string.user_dict_settings_add_dialog_title)
+                .setView(content)
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        onAddOrEditFinished(editText.getText().toString());
+                        if (mAutoReturn) finish();
+                    }})
+                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        if (mAutoReturn) finish();                        
+                    }})
+                .create();
+    }
+
+    @Override
+    protected void onPrepareDialog(int id, Dialog d) {
+        AlertDialog dialog = (AlertDialog) d;
+        EditText editText = (EditText) dialog.findViewById(R.id.edittext);
+        editText.setText(mDialogEditingWord);
+    }
+
+    private void onAddOrEditFinished(String word) {
+        if (mDialogEditingWord != null) {
+            // The user was editing a word, so do a delete/add
+            deleteWord(mDialogEditingWord);
+        }
+        
+        // Disallow duplicates
+        deleteWord(word);
+        
+        // TODO: present UI for picking whether to add word to all locales, or current.
+        UserDictionary.Words.addWord(this, word.toString(),
+                250, UserDictionary.Words.LOCALE_TYPE_ALL);
+        mCursor.requery();
+        mAddedWordAlready = true;
+    }
+
+    private void deleteWord(String word) {
+        getContentResolver().delete(UserDictionary.Words.CONTENT_URI, DELETE_SELECTION,
+                new String[] { word });
+    }
+    
+    private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer {
+        private AlphabetIndexer mIndexer;        
+        
+        public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
+            super(context, layout, c, from, to);
+
+            int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD);
+            String alphabet = context.getString(com.android.internal.R.string.fast_scroll_alphabet);
+            mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet); 
+        }
+
+        public int getPositionForSection(int section) {
+            return mIndexer.getPositionForSection(section);
+        }
+
+        public int getSectionForPosition(int position) {
+            return mIndexer.getSectionForPosition(position);
+        }
+
+        public Object[] getSections() {
+            return mIndexer.getSections();
+        }
+    }
+}
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
new file mode 100644
index 0000000..a23272b
--- /dev/null
+++ b/src/com/android/settings/Utils.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (C) 2007 Google Inc.
+ *
+ * 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.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.preference.Preference;
+import android.preference.PreferenceGroup;
+
+import java.util.List;
+
+public class Utils {
+
+    /**
+     * Set the preference's title to the matching activity's label.
+     */
+    public static final int UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY = 1;
+
+    /**
+     * Finds a matching activity for a preference's intent. If a matching
+     * activity is not found, it will remove the preference.
+     * 
+     * @param context The context.
+     * @param parentPreferenceGroup The preference group that contains the
+     *            preference whose intent is being resolved.
+     * @param preferenceKey The key of the preference whose intent is being
+     *            resolved.
+     * @param flags 0 or one or more of
+     *            {@link #UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY}
+     *            .
+     * @return Whether an activity was found. If false, the preference was
+     *         removed.
+     */
+    public static boolean updatePreferenceToSpecificActivityOrRemove(Context context,
+            PreferenceGroup parentPreferenceGroup, String preferenceKey, int flags) {
+        
+        Preference preference = parentPreferenceGroup.findPreference(preferenceKey);
+        if (preference == null) {
+            return false;
+        }
+        
+        Intent intent = preference.getIntent();
+        if (intent != null) {
+            // Find the activity that is in the system image
+            PackageManager pm = context.getPackageManager();
+            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+            int listSize = list.size();
+            for (int i = 0; i < listSize; i++) {
+                ResolveInfo resolveInfo = list.get(i);
+                if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                        != 0) {
+                    
+                    // Replace the intent with this specific activity
+                    preference.setIntent(new Intent().setClassName(
+                            resolveInfo.activityInfo.packageName,
+                            resolveInfo.activityInfo.name));
+
+                    if ((flags & UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY) != 0) {
+                        // Set the preference title to the activity's label
+                        preference.setTitle(resolveInfo.loadLabel(pm));
+                    }
+                    
+                    return true;
+                }
+            }
+        }
+
+        // Did not find a matching activity, so remove the preference
+        parentPreferenceGroup.removePreference(preference);
+        
+        return true;
+    }
+    
+}
diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java
new file mode 100644
index 0000000..d112915
--- /dev/null
+++ b/src/com/android/settings/WirelessSettings.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.android.settings.bluetooth.BluetoothEnabler;
+import com.android.settings.wifi.WifiEnabler;
+
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.preference.CheckBoxPreference;
+
+public class WirelessSettings extends PreferenceActivity {
+
+    private static final String KEY_TOGGLE_AIRPLANE = "toggle_airplane";
+    private static final String KEY_TOGGLE_BLUETOOTH = "toggle_bluetooth";
+    private static final String KEY_TOGGLE_WIFI = "toggle_wifi";
+
+    private WifiEnabler mWifiEnabler;
+    private AirplaneModeEnabler mAirplaneModeEnabler;
+    private BluetoothEnabler mBtEnabler;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        addPreferencesFromResource(R.xml.wireless_settings);
+
+        initToggles();
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        mWifiEnabler.resume();
+        mAirplaneModeEnabler.resume();
+        mBtEnabler.resume();
+    }
+    
+    @Override
+    protected void onPause() {
+        super.onPause();
+        
+        mWifiEnabler.pause();
+        mAirplaneModeEnabler.pause();
+        mBtEnabler.pause();
+    }
+    
+    private void initToggles() {
+        
+        mWifiEnabler = new WifiEnabler(
+                this,
+                (WifiManager) getSystemService(WIFI_SERVICE),
+                (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI));
+        
+        mAirplaneModeEnabler = new AirplaneModeEnabler(
+                this,
+                (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE));
+        
+        mBtEnabler = new BluetoothEnabler(
+                this,
+                (CheckBoxPreference) findPreference(KEY_TOGGLE_BLUETOOTH));
+    }
+    
+}
diff --git a/src/com/android/settings/ZoneList.java b/src/com/android/settings/ZoneList.java
new file mode 100644
index 0000000..2877f00
--- /dev/null
+++ b/src/com/android/settings/ZoneList.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2006 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.AlarmManager;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * This activity displays a list of time zones that match a filter string
+ * such as "Africa", "Europe", etc. Choosing an item from the list will set
+ * the time zone. Pressing Back without choosing from the list will not 
+ * result in a change in the time zone setting.
+ */
+public class ZoneList extends ListActivity {
+    
+    private static final String TAG = "ZoneList";
+    private static final String KEY_ID = "id";
+    private static final String KEY_DISPLAYNAME = "name";
+    private static final String KEY_GMT = "gmt";
+    private static final String KEY_OFFSET = "offset";
+    private static final String XMLTAG_TIMEZONE = "timezone";
+
+    private static final int HOURS_1 = 60 * 60000;
+    private static final int HOURS_24 = 24 * HOURS_1;
+    private static final int HOURS_HALF = HOURS_1 / 2;
+    
+    private static final int MENU_TIMEZONE = Menu.FIRST+1;
+    private static final int MENU_ALPHABETICAL = Menu.FIRST;
+
+    // Initial focus position
+    private int mDefault;
+    
+    private boolean mSortedByTimezone;
+
+    private SimpleAdapter mTimezoneSortedAdapter;
+    private SimpleAdapter mAlphabeticalAdapter;
+    
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        String[] from = new String[] {KEY_DISPLAYNAME, KEY_GMT};
+        int[] to = new int[] {android.R.id.text1, android.R.id.text2};
+
+        MyComparator comparator = new MyComparator(KEY_OFFSET);
+        
+        List<HashMap> timezoneSortedList = getZones();
+        Collections.sort(timezoneSortedList, comparator);
+        mTimezoneSortedAdapter = new SimpleAdapter(this,
+                (List) timezoneSortedList,
+                android.R.layout.simple_list_item_2,
+                from,
+                to);
+
+        List<HashMap> alphabeticalList = new ArrayList<HashMap>(timezoneSortedList);
+        comparator.setSortingKey(KEY_DISPLAYNAME);
+        Collections.sort(alphabeticalList, comparator);
+        mAlphabeticalAdapter = new SimpleAdapter(this,
+                (List) alphabeticalList,
+                android.R.layout.simple_list_item_2,
+                from,
+                to);
+        
+        // Sets the adapter
+        setSorting(true);
+        
+        // If current timezone is in this list, move focus to it
+        setSelection(mDefault);
+        
+        // Assume user may press Back
+        setResult(RESULT_CANCELED);
+    }
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        menu.add(0, MENU_ALPHABETICAL, 0, R.string.zone_list_menu_sort_alphabetically)
+            .setIcon(android.R.drawable.ic_menu_sort_alphabetically);
+        menu.add(0, MENU_TIMEZONE, 0, R.string.zone_list_menu_sort_by_timezone)
+            .setIcon(R.drawable.ic_menu_3d_globe);
+        
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+     
+        if (mSortedByTimezone) {
+            menu.findItem(MENU_TIMEZONE).setVisible(false);
+            menu.findItem(MENU_ALPHABETICAL).setVisible(true);
+        } else {
+            menu.findItem(MENU_TIMEZONE).setVisible(true);
+            menu.findItem(MENU_ALPHABETICAL).setVisible(false);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+
+            case MENU_TIMEZONE:
+                setSorting(true);
+                return true;
+                
+            case MENU_ALPHABETICAL:
+                setSorting(false);
+                return true;
+                
+            default:
+                return false;
+        }
+    }
+
+    private void setSorting(boolean timezone) {
+        setListAdapter(timezone ? mTimezoneSortedAdapter : mAlphabeticalAdapter);
+        mSortedByTimezone = timezone;
+    }
+    
+    private List<HashMap> getZones() {
+        List<HashMap> myData = new ArrayList<HashMap>();
+        long date = Calendar.getInstance().getTimeInMillis();
+        try {
+            XmlResourceParser xrp = getResources().getXml(R.xml.timezones);
+            while (xrp.next() != XmlResourceParser.START_TAG)
+                ;
+            xrp.next();
+            while (xrp.getEventType() != XmlResourceParser.END_TAG) {
+                while (xrp.getEventType() != XmlResourceParser.START_TAG) {
+                    if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) {
+                        return myData;
+                    }
+                    xrp.next();
+                }
+                if (xrp.getName().equals(XMLTAG_TIMEZONE)) {
+                    String id = xrp.getAttributeValue(0);
+                    String displayName = xrp.nextText();
+                    addItem(myData, id, displayName, date);
+                }
+                while (xrp.getEventType() != XmlResourceParser.END_TAG) {
+                    xrp.next();
+                }
+                xrp.next();
+            }
+            xrp.close();
+        } catch (XmlPullParserException xppe) {
+            Log.e(TAG, "Ill-formatted timezones.xml file");
+        } catch (java.io.IOException ioe) {
+            Log.e(TAG, "Unable to read timezones.xml file");
+        }
+
+        return myData;
+    }
+
+    protected void addItem(List<HashMap> myData, String id, String displayName, 
+            long date) {
+        HashMap map = new HashMap();
+        map.put(KEY_ID, id);
+        map.put(KEY_DISPLAYNAME, displayName);
+        TimeZone tz = TimeZone.getTimeZone(id);
+        int offset = tz.getOffset(date);
+        int p = Math.abs(offset);
+        StringBuilder name = new StringBuilder();
+        name.append("GMT");
+        
+        if (offset < 0) {
+            name.append('-');
+        } else {
+            name.append('+');
+        }
+        
+        name.append(p / (HOURS_1));
+        name.append(':');
+
+        int min = p / 60000;
+        min %= 60;
+
+        if (min < 10) {
+            name.append('0');
+        }
+        name.append(min);
+        
+        map.put(KEY_GMT, name.toString());
+        map.put(KEY_OFFSET, offset);
+        
+        if (id.equals(TimeZone.getDefault().getID())) {
+            mDefault = myData.size();
+        }
+        
+        myData.add(map);
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Map map = (Map) l.getItemAtPosition(position);
+        // Update the system timezone value
+        AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+        alarm.setTimeZone((String) map.get(KEY_ID));
+        setResult(RESULT_OK);
+        finish();
+    }
+    
+    private static class MyComparator implements Comparator<HashMap> {
+        private String mSortingKey; 
+        
+        public MyComparator(String sortingKey) {
+            mSortingKey = sortingKey;
+        }
+        
+        public void setSortingKey(String sortingKey) {
+            mSortingKey = sortingKey;
+        }
+        
+        public int compare(HashMap map1, HashMap map2) {
+            Object value1 = map1.get(mSortingKey);
+            Object value2 = map2.get(mSortingKey);
+
+            /* 
+             * This should never happen, but just in-case, put non-comparable
+             * items at the end.
+             */
+            if (!isComparable(value1)) {
+                return isComparable(value2) ? 1 : 0;
+            } else if (!isComparable(value2)) {
+                return -1;
+            }
+            
+            return ((Comparable) value1).compareTo(value2);
+        }
+        
+        private boolean isComparable(Object value) {
+            return (value != null) && (value instanceof Comparable); 
+        }
+    }
+    
+}
diff --git a/src/com/android/settings/ZonePicker.java b/src/com/android/settings/ZonePicker.java
new file mode 100644
index 0000000..def5036
--- /dev/null
+++ b/src/com/android/settings/ZonePicker.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2006 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.ListActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ZonePicker extends ListActivity {
+
+    private ArrayAdapter<CharSequence> mFilterAdapter;
+    
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mFilterAdapter = ArrayAdapter.createFromResource(this,
+                R.array.timezone_filters, android.R.layout.simple_list_item_1);
+        setListAdapter(mFilterAdapter);
+    }
+    
+    protected void addItem(List<Map> data, String name, String zone) {
+        HashMap temp = new HashMap();
+        temp.put("title", name);
+        temp.put("zone", zone);
+        data.add(temp);
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        String filter = (String) mFilterAdapter.getItem(position);
+        // If All is chosen, reset the filter
+        if (filter.equals("All")) {
+            filter = null;
+        }
+        Intent zoneList = new Intent();
+        zoneList.setClass(this, ZoneList.class);
+        zoneList.putExtra("filter", filter);
+        
+        startActivityForResult(zoneList, 0);
+    }
+    
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        // If subactivity has resulted in a timezone selection, close this act.
+        if (resultCode == RESULT_OK) {
+            finish();
+        }
+    }    
+}
diff --git a/src/com/android/settings/battery_history/BatteryHistory.java b/src/com/android/settings/battery_history/BatteryHistory.java
new file mode 100644
index 0000000..dcf6cbf
--- /dev/null
+++ b/src/com/android/settings/battery_history/BatteryHistory.java
@@ -0,0 +1,861 @@
+/*
+ * Copyright (C) 2006 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.battery_history;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.settings.R;
+
+import android.app.Activity;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.BatteryStats.Timer;
+import android.os.BatteryStats.Uid;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+public class BatteryHistory extends Activity implements OnClickListener, OnItemSelectedListener {
+    private static final String TAG = "BatteryHistory";
+
+    private static final int SECONDS_PER_MINUTE = 60;
+    private static final int SECONDS_PER_HOUR = 60 * 60;
+    private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+    
+    // Must be in sync with the values in res/values/array.xml (id battery_history_type_spinner)
+    private static final int CPU_USAGE = 0;
+    private static final int NETWORK_USAGE = 1;
+    private static final int GPS_USAGE = 2;
+    private static final int SENSOR_USAGE = 3;
+    private static final int WAKELOCK_USAGE = 4;
+    private static final int MISC_USAGE = 5;
+
+    // Must be in sync with the values in res/values/array.xml (id battery_history_which_spinner)
+    private static final int UNPLUGGED = 0;
+    private static final int CURRENT = 1;
+    private static final int TOTAL = 2;
+    
+    private BatteryStats mStats;
+    private int mWhich = BatteryStats.STATS_UNPLUGGED;
+    private int mType = CPU_USAGE;
+    
+    private GraphableButton[] mButtons;
+    IBatteryStats mBatteryInfo;
+    
+    private List<CpuUsage> mCpuUsage = new ArrayList<CpuUsage>();
+    private List<NetworkUsage> mNetworkUsage = new ArrayList<NetworkUsage>();
+    private List<SensorUsage> mSensorUsage = new ArrayList<SensorUsage>();
+    private List<SensorUsage> mGpsUsage = new ArrayList<SensorUsage>();
+    private List<WakelockUsage> mWakelockUsage = new ArrayList<WakelockUsage>();
+    private List<MiscUsage> mMiscUsage = new ArrayList<MiscUsage>();
+    
+    private boolean mHaveCpuUsage, mHaveNetworkUsage, mHaveSensorUsage,
+            mHaveWakelockUsage, mHaveMiscUsage;
+    
+    private LinearLayout mGraphLayout;
+    private LinearLayout mTextLayout;
+    private TextView mMessageText;
+    private TextView mDetailsText;
+    private Button mDetailsBackButton;
+    private Spinner mTypeSpinner;
+    private Spinner mWhichSpinner;
+    
+    private boolean mDetailsShown = false;
+    
+    private static String getLabel(String packageName, PackageManager pm) {
+        try {
+            ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+            CharSequence label = ai.loadLabel(pm);
+            if (label != null) {
+                return label.toString();
+            }
+        } catch (NameNotFoundException e) {
+            return packageName;
+        }
+        
+        return "";
+    }
+    
+    void formatTime(double millis, StringBuilder sb) {
+        int seconds = (int) Math.floor(millis / 1000);
+        
+        int days = 0, hours = 0, minutes = 0;
+        if (seconds > SECONDS_PER_DAY) {
+            days = seconds / SECONDS_PER_DAY;
+            seconds -= days * SECONDS_PER_DAY;
+        }
+        if (seconds > SECONDS_PER_HOUR) {
+            hours = seconds / SECONDS_PER_HOUR;
+            seconds -= hours * SECONDS_PER_HOUR;
+        }
+        if (seconds > SECONDS_PER_MINUTE) {
+            minutes = seconds / SECONDS_PER_MINUTE;
+            seconds -= minutes * SECONDS_PER_MINUTE;
+        }
+        if (days > 0) {
+            sb.append(getString(R.string.battery_history_days, days, hours, minutes, seconds));
+        } else if (hours > 0) {
+            sb.append(getString(R.string.battery_history_hours, hours, minutes, seconds));
+        } else if (minutes > 0) { 
+            sb.append(getString(R.string.battery_history_minutes, minutes, seconds));
+        } else {
+            sb.append(getString(R.string.battery_history_seconds, seconds));
+        }
+    }
+    
+    abstract class Graphable implements Comparable<Graphable> {        
+        protected String mName;
+        protected String mNamePackage;
+        protected boolean mUniqueName;
+        protected String[] mPackages;
+        protected String[] mPackageNames;
+        
+        public abstract String getLabel();
+        public abstract double getSortValue();
+        public abstract double[] getValues();
+        public abstract void getInfo(StringBuilder info);
+        
+        public double getMaxValue() {
+            return -Double.MAX_VALUE;            
+        }
+        
+        public int compareTo(Graphable o) {
+            double t = getSortValue();
+            double ot = o.getSortValue();
+            if (t < ot) {
+                // Largest first
+                return 1;
+            } else if (t > ot) {
+                return -1;
+            } else {
+                return 0;
+            }
+        }
+                
+        // Side effects: sets mName and mUniqueName
+        void getNameForUid(int uid) {
+            PackageManager pm = getPackageManager();
+            mPackages = pm.getPackagesForUid(uid);
+            if (mPackages == null) {
+                mName = Integer.toString(uid);
+                mNamePackage = null;
+                return;
+            }
+            
+            mPackageNames = new String[mPackages.length];
+            System.arraycopy(mPackages, 0, mPackageNames, 0, mPackages.length);
+            
+            // Convert package names to user-facing labels where possible
+            for (int i = 0; i < mPackageNames.length; i++) {
+                mPackageNames[i] = BatteryHistory.getLabel(mPackageNames[i], pm);
+            }
+
+            if (mPackageNames.length == 1) {
+                mNamePackage = mPackages[0];
+                mName = mPackageNames[0];
+                mUniqueName = true;
+            } else {
+                mName = getString(R.string.battery_history_uid, uid); // Default name
+                // Look for an official name for this UID.
+                for (String name : mPackages) {
+                    try {
+                        PackageInfo pi = pm.getPackageInfo(name, 0);
+                        if (pi.sharedUserLabel != 0) {
+                            CharSequence nm = pm.getText(name,
+                                    pi.sharedUserLabel, pi.applicationInfo);
+                            if (nm != null) {
+                                mName = nm.toString();
+                                break;
+                            }
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    class CpuUsage extends Graphable {
+        String mProcess;
+        double[] mUsage;
+        double mTotalRuntime;
+        long mStarts;
+        
+        public CpuUsage(int uid, String process, long userTime, long systemTime,
+                long starts, long totalRuntime) {
+            getNameForUid(uid);
+            mProcess = process;
+            PackageManager pm = BatteryHistory.this.getPackageManager();
+            mName = BatteryHistory.getLabel(process, pm);
+            mUsage = new double[2];
+            
+            mUsage[0] = userTime;
+            mUsage[1] = userTime + systemTime;
+            mTotalRuntime = totalRuntime;
+            mStarts = starts;
+        }
+        
+        public String getLabel() {
+            return mName;
+        }
+        
+        public double getSortValue() {
+            return mUsage[1];
+        }
+        
+        public double[] getValues() {
+            return mUsage;
+        }
+        
+        public double getMaxValue() {
+            return mTotalRuntime;            
+        }
+        
+        public void getInfo(StringBuilder info) {
+            info.append(getString(R.string.battery_history_cpu_usage, mProcess));
+            info.append("\n\n");
+            info.append(getString(R.string.battery_history_user_time));
+            formatTime(mUsage[0] * 10, info);
+            info.append('\n');
+            info.append(getString(R.string.battery_history_system_time));
+            formatTime((mUsage[1] - mUsage[0]) * 10, info);
+            info.append('\n');
+            info.append(getString(R.string.battery_history_total_time));
+            formatTime((mUsage[1]) * 10, info);
+            info.append('\n');
+            info.append(getString(R.string.battery_history_starts, mStarts));
+        }
+    }
+    
+    class NetworkUsage extends Graphable {
+        double[] mUsage;
+        
+        public NetworkUsage(int uid, long received, long sent) {
+            getNameForUid(uid);
+            
+            mUsage = new double[2];
+            mUsage[0] = received;
+            mUsage[1] = received + sent;
+        }
+        
+        public String getLabel() {
+            return mName;
+        }
+        
+        public double getSortValue() {
+            return mUsage[1];
+        }
+        
+        public double[] getValues() {
+            return mUsage;
+        }
+        
+        public void getInfo(StringBuilder info) {
+            info.append(getString(R.string.battery_history_network_usage, mName));
+            info.append("\n\n");
+            info.append(getString(R.string.battery_history_bytes_received, (long) mUsage[0]));
+            info.append('\n');
+            info.append(getString(R.string.battery_history_bytes_sent,
+                    (long) mUsage[1] - (long) mUsage[0]));
+            info.append('\n');
+            info.append(getString(R.string.battery_history_bytes_total, (long) mUsage[1]));
+
+            if (!mUniqueName) {
+                info.append("\n\n");
+                info.append(getString(R.string.battery_history_packages_sharing_this_uid));
+                info.append('\n');
+
+                PackageManager pm = BatteryHistory.this.getPackageManager();
+                List<String> names = new ArrayList<String>();
+                for (String name : mPackageNames) {
+                    names.add(BatteryHistory.getLabel(name, pm));
+                }
+                Collections.sort(names);
+                for (String name : names) {
+                    info.append("    ");
+                    info.append(name);
+                    info.append('\n');
+                }
+            }
+        }
+    }
+    
+    class SensorUsage extends Graphable {
+        double[] mUsage;
+        double mTotalRealtime;
+        int mCount;
+        
+        public SensorUsage(int uid, long time, int count, long totalRealtime) {
+            getNameForUid(uid);
+            
+            mUsage = new double[1];
+            mUsage[0] = time;
+            mTotalRealtime = totalRealtime;
+            
+            mCount = count;
+        }
+        
+        public String getLabel() {
+            return mName;
+        }
+        
+        public double getSortValue() {
+            return mUsage[0];
+        }
+        
+        public double[] getValues() {
+            return mUsage;
+        }
+        
+        public double getMaxValue() {
+            return mTotalRealtime;            
+        }
+        
+        public void getInfo(StringBuilder info) {
+            info.append(getString(R.string.battery_history_sensor));
+            info.append(mName);
+            info.append("\n\n");
+            info.append(getString(R.string.battery_history_total_time));
+            formatTime(mUsage[0], info);
+            info.append("\n\n");
+        }
+    }
+    
+    
+    class WakelockUsage extends Graphable {
+        double[] mUsage;
+        double mTotalRealtime;
+        int mCount;
+        
+        public WakelockUsage(int uid, long time, int count, long totalRealtime) {
+            getNameForUid(uid);
+            
+            mUsage = new double[1];
+            mUsage[0] = time;
+            mTotalRealtime = totalRealtime;
+            
+            mCount = count;
+        }
+        
+        public String getLabel() {
+            return mName;
+        }
+        
+        public double getSortValue() {
+            return mUsage[0];
+        }
+        
+        public double[] getValues() {
+            return mUsage;
+        }
+        
+        public double getMaxValue() {
+            return mTotalRealtime;            
+        }
+        
+        public void getInfo(StringBuilder info) {
+            info.append(getString(R.string.battery_history_wakelock));
+            info.append(mName);
+            info.append("\n\n");
+            info.append(getString(R.string.battery_history_total_time));
+            formatTime(mUsage[0], info);
+            info.append("\n\n");
+        }
+    }
+    
+    class MiscUsage extends Graphable {
+        int mInfoLabelRes;
+        double[] mUsage;
+        double mTotalRealtime;
+        
+        public MiscUsage(String name, int infoLabelRes, long value,
+                long totalRealtime) {
+            mName = name;
+            
+            mInfoLabelRes = infoLabelRes;
+            
+            mUsage = new double[2];
+            mUsage[0] = value;
+            mTotalRealtime = totalRealtime;
+        }
+        
+        public String getLabel() {
+            return mName;
+        }
+        
+        public double getSortValue() {
+            return mUsage[1];
+        }
+        
+        public double[] getValues() {
+            return mUsage;
+        }
+        
+        public double getMaxValue() {
+            return mTotalRealtime;            
+        }
+        
+        public void getInfo(StringBuilder info) {
+            info.append(getString(mInfoLabelRes));
+            info.append(' ');
+            formatTime(mUsage[0], info);
+            info.append(" (");
+            info.append((mUsage[0]*100)/mTotalRealtime);
+            info.append("%)");
+        }
+    }
+    
+    private List<? extends Graphable> getGraphRecords() {
+        switch (mType) {
+            case CPU_USAGE: return mCpuUsage;
+            case NETWORK_USAGE : return mNetworkUsage;
+            case SENSOR_USAGE: return mSensorUsage;
+            case GPS_USAGE: return mGpsUsage;
+            case WAKELOCK_USAGE: return mWakelockUsage;
+            case MISC_USAGE: return mMiscUsage;
+            default:
+                return (List<? extends Graphable>) null; // TODO
+        }
+    }
+    
+    private void displayGraph() {
+        Log.i(TAG, "displayGraph");
+
+        collectStatistics();
+        
+        // Hide the UI and selectively enable it below
+        mMessageText.setVisibility(View.GONE);
+        for (int i = 0; i < mButtons.length; i++) {
+            mButtons[i].setVisibility(View.INVISIBLE);
+        }
+        
+        double maxValue = -Double.MAX_VALUE;
+        
+        List<? extends Graphable> records = getGraphRecords();
+        for (Graphable g : records) {
+            double[] values = g.getValues();
+            maxValue = Math.max(maxValue, values[values.length - 1]);
+            maxValue = Math.max(maxValue, g.getMaxValue());
+        }
+        
+        int[] colors = new int[2];
+        colors[0] = 0xff0000ff;
+        colors[1] = 0xffff0000;
+        
+        for (int i = 0; i < mButtons.length; i++) {
+            mButtons[i].setVisibility(View.INVISIBLE);
+        }
+        
+        int numRecords = Math.min(records.size(), mButtons.length);
+        if (numRecords == 0) {
+             mMessageText.setVisibility(View.VISIBLE);
+             mMessageText.setText(R.string.battery_history_no_data);
+        } else {
+            for (int i = 0; i < numRecords; i++) {
+                Graphable r = records.get(i);           
+
+                mButtons[i].setText(r.getLabel());
+                mButtons[i].setValues(r.getValues(), maxValue);
+                mButtons[i].setVisibility(View.VISIBLE);
+            }
+        }
+    }
+    
+    private void hideDetails() {
+        mTextLayout.setVisibility(View.GONE);
+        mGraphLayout.setVisibility(View.VISIBLE);
+        mDetailsShown = false;
+    }
+    
+    private void showDetails(int id) {
+        mGraphLayout.setVisibility(View.GONE);
+        mTextLayout.setVisibility(View.VISIBLE);
+            
+        StringBuilder info = new StringBuilder();
+        List<? extends Graphable> records = getGraphRecords();
+        if (id < records.size()) {
+            Graphable record = records.get(id);
+            record.getInfo(info);
+        } else {
+            info.append(getString(R.string.battery_history_details_for, id));
+        }
+        mDetailsText.setText(info.toString());
+        mDetailsShown = true;
+    }
+
+    private void processCpuUsage() {
+        mCpuUsage.clear();
+        
+        long uSecTime = SystemClock.uptimeMillis() * 1000;
+        final long uSecNow = mStats.computeBatteryUptime(uSecTime, mWhich) / 1000;
+        
+        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+        final int NU = uidStats.size();
+        for (int iu = 0; iu < NU; iu++) {
+            Uid u = uidStats.valueAt(iu);
+
+            Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+            if (processStats.size() > 0) {
+                for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
+                        : processStats.entrySet()) {
+
+                    Uid.Proc ps = ent.getValue();
+                    long userTime = ps.getUserTime(mWhich);
+                    long systemTime = ps.getSystemTime(mWhich);
+                    long starts = ps.getStarts(mWhich);
+
+                    if (userTime != 0 || systemTime != 0) {
+                        mCpuUsage.add(new CpuUsage(u.getUid(), ent.getKey(),
+                                userTime, systemTime, starts, uSecNow));
+                    }
+                }
+            }
+        }
+        Collections.sort(mCpuUsage);
+    }
+    
+    private void processNetworkUsage() {
+        mNetworkUsage.clear();
+        
+        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+        final int NU = uidStats.size();
+        for (int iu = 0; iu < NU; iu++) {
+            Uid u = uidStats.valueAt(iu);
+            
+            long received = u.getTcpBytesReceived(mWhich);
+            long sent = u.getTcpBytesSent(mWhich);
+            if (received + sent > 0) {
+                mNetworkUsage.add(new NetworkUsage(u.getUid(), received, sent));
+            }
+        }
+        Collections.sort(mNetworkUsage);
+    }
+    
+    private void processSensorUsage() {
+        mGpsUsage.clear();
+        mSensorUsage.clear();
+        
+        long uSecTime = SystemClock.elapsedRealtime() * 1000;
+        final long uSecNow = mStats.computeBatteryRealtime(uSecTime, mWhich) / 1000;
+        
+        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+        final int NU = uidStats.size();
+        for (int iu = 0; iu < NU; iu++) {
+            Uid u = uidStats.valueAt(iu);
+            int uid = u.getUid();
+            
+            Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
+            long timeGps = 0;
+            int countGps = 0;
+            long timeOther = 0;
+            int countOther = 0;
+            if (sensorStats.size() > 0) {
+                for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> ent
+                        : sensorStats.entrySet()) {
+
+                    Uid.Sensor se = ent.getValue();
+                    int handle = se.getHandle();
+                    Timer timer = se.getSensorTime();
+                    if (timer != null) {
+                        // Convert from microseconds to milliseconds with rounding
+                        long totalTime = (timer.getTotalTime(uSecNow, mWhich) + 500) / 1000;
+                        int count = timer.getCount(mWhich);
+                        if (handle == BatteryStats.Uid.Sensor.GPS) {
+                            timeGps += totalTime;
+                            countGps += count;
+                        } else {
+                            timeOther += totalTime;
+                            countOther += count;
+                        }
+                    }
+                }
+            }
+            
+            if (timeGps > 0) {
+                mGpsUsage.add(new SensorUsage(uid, timeGps, countGps, uSecNow));
+            }
+            if (timeOther > 0) {
+                mSensorUsage.add(new SensorUsage(uid, timeOther, countOther, uSecNow));
+            }
+        }
+        
+        Collections.sort(mGpsUsage);
+        Collections.sort(mSensorUsage);
+    }
+    
+    private void processWakelockUsage() {
+        mWakelockUsage.clear();
+        
+        long uSecTime = SystemClock.elapsedRealtime() * 1000;
+        final long uSecNow = mStats.computeBatteryRealtime(uSecTime, mWhich) / 1000;
+        
+        SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+        final int NU = uidStats.size();
+        for (int iu = 0; iu < NU; iu++) {
+            Uid u = uidStats.valueAt(iu);
+            int uid = u.getUid();
+            
+            Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
+            long time = 0;
+            int count = 0;
+            if (wakelockStats.size() > 0) {
+                for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> ent
+                        : wakelockStats.entrySet()) {
+
+                    Uid.Wakelock wl = ent.getValue();
+                    Timer timer = wl.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
+                    if (timer != null) {
+                        // Convert from microseconds to milliseconds with rounding
+                        time += (timer.getTotalTime(uSecNow, mWhich) + 500) / 1000;
+                        count += timer.getCount(mWhich);
+                    }
+                }
+            }
+            
+            if (time > 0) {
+                mWakelockUsage.add(new WakelockUsage(uid, time, count, uSecNow));
+            }
+        }
+        
+        Collections.sort(mWakelockUsage);
+    }
+    
+    private void processMiscUsage() {
+        mMiscUsage.clear();
+        
+        long rawRealtime = SystemClock.elapsedRealtime() * 1000;
+        final long batteryRealtime = mStats.getBatteryRealtime(rawRealtime);
+        final long whichRealtime = mStats.computeBatteryRealtime(rawRealtime, mWhich) / 1000;
+        
+        long time = mStats.computeBatteryUptime(SystemClock.uptimeMillis() * 1000, mWhich) / 1000;
+        if (time > 0) {
+            mMiscUsage.add(new MiscUsage(getString(
+                    R.string.battery_history_awake_label),
+                    R.string.battery_history_awake,
+                    time, whichRealtime)); 
+        }
+        
+        time = mStats.getScreenOnTime(batteryRealtime, mWhich) / 1000;
+        if (time > 0) {
+            mMiscUsage.add(new MiscUsage(getString(
+                    R.string.battery_history_screen_on_label),
+                    R.string.battery_history_screen_on,
+                    time, whichRealtime)); 
+        }
+        
+        time = mStats.getPhoneOnTime(batteryRealtime, mWhich) / 1000;
+        if (time > 0) {
+            mMiscUsage.add(new MiscUsage(getString(
+                    R.string.battery_history_phone_on_label),
+                    R.string.battery_history_phone_on,
+                    time, whichRealtime)); 
+        }
+        
+        Collections.sort(mMiscUsage);
+    }
+    
+    private void collectStatistics() {
+        if (mType == CPU_USAGE) {
+            if (!mHaveCpuUsage) {
+                mHaveCpuUsage = true;
+                processCpuUsage();
+            }
+        }
+        if (mType == NETWORK_USAGE) {
+            if (!mHaveNetworkUsage) {
+                mHaveNetworkUsage = true;
+                processNetworkUsage();
+            }
+        }
+        if (mType == GPS_USAGE || mType == SENSOR_USAGE) {
+            if (!mHaveSensorUsage) {
+                mHaveSensorUsage = true;
+                processSensorUsage();
+            }
+        }
+        if (mType == WAKELOCK_USAGE) {
+            if (!mHaveWakelockUsage) {
+                mHaveWakelockUsage = true;
+                processWakelockUsage();
+            }
+        }
+        if (mType == MISC_USAGE) {
+            if (!mHaveMiscUsage) {
+                mHaveMiscUsage = true;
+                processMiscUsage();
+            }
+        }
+    }
+    
+    private void load() {
+        try {
+            byte[] data = mBatteryInfo.getStatistics();
+            Parcel parcel = Parcel.obtain();
+            //Log.i(TAG, "Got data: " + data.length + " bytes");
+            parcel.unmarshall(data, 0, data.length);
+            parcel.setDataPosition(0);
+            mStats = com.android.internal.os.BatteryStatsImpl.CREATOR
+                    .createFromParcel(parcel);
+            //Log.i(TAG, "RECEIVED BATTERY INFO:");
+            //mStats.dumpLocked(new LogPrinter(Log.INFO, TAG));
+            
+            mHaveCpuUsage =  mHaveNetworkUsage =  mHaveSensorUsage
+                    = mHaveWakelockUsage = mHaveMiscUsage = false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException:", e);
+        }
+    }
+    
+    public void onClick(View v) {
+        if (v == mDetailsBackButton) {
+            hideDetails();
+            return;
+        }
+        
+        int id = ((Integer) v.getTag()).intValue();
+        showDetails(id);
+    }
+    
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && mDetailsShown) {
+            hideDetails();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        int oldWhich = mWhich;
+        
+        if (parent.equals(mTypeSpinner)) {
+            mType = position;
+        } else if (parent.equals(mWhichSpinner)) {
+            switch (position) {
+                case UNPLUGGED:
+                    mWhich = BatteryStats.STATS_UNPLUGGED;
+                    break;
+                case CURRENT:
+                    mWhich = BatteryStats.STATS_CURRENT;
+                    break;
+                case TOTAL:
+                    mWhich = BatteryStats.STATS_TOTAL;
+                    break;
+            }
+        }
+        
+        if (oldWhich != mWhich) {
+            mHaveCpuUsage =  mHaveNetworkUsage =  mHaveSensorUsage
+                    = mHaveWakelockUsage = mHaveMiscUsage = false;
+        }
+        
+        displayGraph();
+    }
+
+    public void onNothingSelected(AdapterView<?> parent) {
+        // Do nothing
+    }
+    
+    @Override
+    public Object onRetainNonConfigurationInstance() {
+        BatteryStats stats = mStats;
+        mStats = null;
+        return stats;
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (mStats != null) {
+            outState.putParcelable("stats", mStats);
+        }
+        outState.putInt("type", mType);
+        outState.putInt("which", mWhich);
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "onCreate");
+        
+        setContentView(R.layout.battery_history);
+        
+        mGraphLayout = (LinearLayout) findViewById(R.id.graphLayout);
+        mTextLayout = (LinearLayout) findViewById(R.id.textLayout);
+        mDetailsText = (TextView) findViewById(R.id.detailsText);
+        mMessageText = (TextView) findViewById(R.id.messageText);
+        
+        mTypeSpinner = (Spinner) findViewById(R.id.typeSpinner);
+        mTypeSpinner.setOnItemSelectedListener(this);
+        
+        mWhichSpinner = (Spinner) findViewById(R.id.whichSpinner);
+        mWhichSpinner.setOnItemSelectedListener(this);
+        mWhichSpinner.setEnabled(true);
+        
+        mButtons = new GraphableButton[8];
+        mButtons[0] = (GraphableButton) findViewById(R.id.button0);
+        mButtons[1] = (GraphableButton) findViewById(R.id.button1);
+        mButtons[2] = (GraphableButton) findViewById(R.id.button2);
+        mButtons[3] = (GraphableButton) findViewById(R.id.button3);
+        mButtons[4] = (GraphableButton) findViewById(R.id.button4);
+        mButtons[5] = (GraphableButton) findViewById(R.id.button5);
+        mButtons[6] = (GraphableButton) findViewById(R.id.button6);
+        mButtons[7] = (GraphableButton) findViewById(R.id.button7);
+        
+        for (int i = 0; i < mButtons.length; i++) {
+            mButtons[i].setTag(i);
+            mButtons[i].setOnClickListener(this);
+        }
+        
+        mBatteryInfo = IBatteryStats.Stub.asInterface(
+                ServiceManager.getService("batteryinfo"));
+        
+        mStats = (BatteryStats)getLastNonConfigurationInstance();
+        if (icicle != null) {
+            if (mStats == null) {
+                mStats = (BatteryStats)icicle.getParcelable("stats");
+            }
+            mType = icicle.getInt("type");
+            mWhich = icicle.getInt("which");
+        }
+        if (mStats == null) {
+            load();
+        }
+        displayGraph();
+    }
+}
diff --git a/src/com/android/settings/battery_history/GraphableButton.java b/src/com/android/settings/battery_history/GraphableButton.java
new file mode 100644
index 0000000..39028d0
--- /dev/null
+++ b/src/com/android/settings/battery_history/GraphableButton.java
@@ -0,0 +1,55 @@
+package com.android.settings.battery_history;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.Button;
+
+public class GraphableButton extends Button {
+    private static final String TAG = "GraphableButton";
+
+    static Paint[] sPaint = new Paint[2];
+    static {
+        sPaint[0] = new Paint();
+        sPaint[0].setStyle(Paint.Style.FILL);
+        sPaint[0].setColor(Color.BLUE);
+        
+        sPaint[1] = new Paint();
+        sPaint[1].setStyle(Paint.Style.FILL);
+        sPaint[1].setColor(Color.RED);
+    }
+    
+    double[] mValues;
+    
+    public GraphableButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+    
+    public void setValues(double[] values, double maxValue) {
+        mValues = values.clone();
+        for (int i = 0; i < values.length; i++) {
+            mValues[i] /= maxValue;
+        }
+    }
+    
+    @Override
+    public void onDraw(Canvas canvas) {
+        Log.i(TAG, "onDraw: w = " + getWidth() + ", h = " + getHeight());
+        
+        int xmin = getPaddingLeft();
+        int xmax = getWidth() - getPaddingRight();
+        int ymin = getPaddingTop();
+        int ymax = getHeight() - getPaddingBottom();
+        
+        int startx = xmin;
+        for (int i = 0; i < mValues.length; i++) {
+            int endx = xmin + (int) (mValues[i] * (xmax - xmin));
+            canvas.drawRect(startx, ymin, endx, ymax, sPaint[i]);
+            startx = endx;
+        }
+        super.onDraw(canvas);
+    }
+}
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..a51f9b5e
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java
@@ -0,0 +1,198 @@
+/*
+ * 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.BluetoothError;
+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) {
+            if (BluetoothIntent.SCAN_MODE_CHANGED_ACTION.equals(intent.getAction())) {
+                int mode = intent.getIntExtra(BluetoothIntent.SCAN_MODE, BluetoothError.ERROR);
+                if (mode != BluetoothError.ERROR) {
+                    handleModeChanged(mode);
+                }
+            }
+        }
+    };
+
+    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;
+        }
+
+        IntentFilter filter = new IntentFilter(BluetoothIntent.SCAN_MODE_CHANGED_ACTION);
+        filter.addAction(BluetoothIntent.DISABLED_ACTION);
+        mContext.registerReceiver(mReceiver, filter);
+        mCheckBoxPreference.setOnPreferenceChangeListener(this);
+        
+        handleModeChanged(mLocalManager.getBluetoothManager().getScanMode());
+    }
+    
+    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.setScanMode(BluetoothDevice.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+        } else {
+            manager.setScanMode(BluetoothDevice.SCAN_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.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+            mCheckBoxPreference.setChecked(true);
+            updateCountdownSummary();
+            
+        } else {
+            mCheckBoxPreference.setChecked(false);
+        }
+    }
+    
+    private void updateCountdownSummary() {
+        int mode = mLocalManager.getBluetoothManager().getScanMode();
+        if (mode != BluetoothDevice.SCAN_MODE_CONNECTABLE_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..2ad5726
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
@@ -0,0 +1,147 @@
+/*
+ * 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.BluetoothError;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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 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.BOND_STATE_CHANGED_ACTION)) {
+                int bondState = intent.getIntExtra(BluetoothIntent.BOND_STATE,
+                                                   BluetoothError.ERROR);
+                mManager.getLocalDeviceManager().onBondingStateChanged(address, bondState);
+                if (bondState == BluetoothDevice.BOND_NOT_BONDED) {
+                    int reason = intent.getIntExtra(BluetoothIntent.REASON, BluetoothError.ERROR);
+                    if (reason == BluetoothDevice.UNBOND_REASON_AUTH_FAILED ||
+                            reason == BluetoothDevice.UNBOND_REASON_AUTH_REJECTED ||
+                            reason == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN) {
+                        mManager.getLocalDeviceManager().onBondingError(address, reason);
+                    }
+                }
+                
+            } 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) {
+                    Log.i(TAG, "Failed to connect BT headset");
+                }
+                
+            } 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) {
+                    Log.i(TAG, "Failed to connect BT A2DP");
+                }
+                
+            } else if (action.equals(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION)) {
+                mManager.getLocalDeviceManager().onBtClassChanged(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.BOND_STATE_CHANGED_ACTION);
+        
+        // Fine-grained state broadcasts
+        filter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
+        filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
+        filter.addAction(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION);
+        
+        mManager.getContext().registerReceiver(mBroadcastReceiver, filter);
+    }
+    
+    public void stop() {
+        mManager.getContext().unregisterReceiver(mBroadcastReceiver);   
+    }
+}
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..5e289f7
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothPinDialog.java
@@ -0,0 +1,202 @@
+/*
+ * 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.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.text.InputFilter.LengthFilter;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+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,
+        TextWatcher {
+    private static final String TAG = "BluetoothPinDialog";
+
+    private LocalBluetoothManager mLocalManager;
+    private String mAddress;
+    private EditText mPinView;
+    private Button mOkButton;
+
+    private static final String INSTANCE_KEY_PAIRING_CANCELED = "received_pairing_canceled";
+    private boolean mReceivedPairingCanceled;
+    
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!BluetoothIntent.PAIRING_CANCEL_ACTION.equals(intent.getAction())) {
+                return;
+            }
+            
+            String address = intent.getStringExtra(BluetoothIntent.ADDRESS);
+            if (address == null || address.equals(mAddress)) {
+                onReceivedPairingCanceled();
+            }
+        }
+    };
+    
+    @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();
+        
+        mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+        mOkButton.setEnabled(false);
+
+        /*
+         * Leave this registered through pause/resume since we still want to
+         * finish the activity in the background if pairing is canceled.
+         */
+        registerReceiver(mReceiver, new IntentFilter(BluetoothIntent.PAIRING_CANCEL_ACTION));
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        
+        mReceivedPairingCanceled = savedInstanceState.getBoolean(INSTANCE_KEY_PAIRING_CANCELED);
+        if (mReceivedPairingCanceled) {
+            onReceivedPairingCanceled();
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        
+        outState.putBoolean(INSTANCE_KEY_PAIRING_CANCELED, mReceivedPairingCanceled);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        
+        unregisterReceiver(mReceiver);
+    }
+
+    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);
+        mPinView.addTextChangedListener(this);
+        // Maximum of 10 characters in a PIN
+        mPinView.setFilters(new InputFilter[] { new LengthFilter(10) });
+        
+        return view;
+    }
+
+    public void afterTextChanged(Editable s) {
+        if (s.length() > 0) {
+            mOkButton.setEnabled(true);
+        }
+    }
+
+    private void onReceivedPairingCanceled() {
+        mReceivedPairingCanceled = true;
+        
+        TextView messageView = (TextView) findViewById(R.id.message);
+        messageView.setText(getString(R.string.bluetooth_pairing_error_message,
+                mLocalManager.getLocalDeviceManager().getName(mAddress)));
+        
+        mPinView.setVisibility(View.GONE);
+        mPinView.clearFocus();
+        mPinView.removeTextChangedListener(this);
+
+        mOkButton.setEnabled(true);
+        mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+    }
+    
+    private void onPair(String pin) {
+        byte[] pinBytes = BluetoothDevice.convertPinToBytes(pin);
+        
+        if (pinBytes == null) {
+            return;
+        }
+        
+        mLocalManager.getBluetoothManager().setPin(mAddress, pinBytes);
+    }
+
+    private void onCancel() {
+        mLocalManager.getBluetoothManager().cancelBondProcess(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;
+        }
+    }
+
+    /* Not used */
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    /* Not used */
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+}
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..5adada3
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.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.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(com.android.internal.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);
+        } else if (bluetoothState == ExtendedBluetoothState.DISABLED) {
+            mDeviceList.setProgress(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..7dd1b70
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ConnectSpecificProfilesActivity.java
@@ -0,0 +1,298 @@
+/*
+ * 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.text.TextUtils;
+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();
+    }
+
+    @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) {
+        setOnlineMode(checked, true);
+    }
+    
+    private void onProfileCheckedStateChanged(Profile profile, boolean checked) {
+        if (mOnlineMode) {
+            if (checked) {
+                mDevice.connect(profile);
+            } else {
+                mDevice.disconnect(profile);
+            }
+        }
+        
+        LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager
+                .getProfileManager(mManager, profile);
+        profileManager.setPreferred(mDevice.getAddress(), checked);
+    }
+    
+    public void onDeviceAttributesChanged(LocalBluetoothDevice device) {
+        refresh();
+    }
+
+    private void refresh() {
+        // We are in 'online mode' if we are connected, connecting, or disconnecting
+        setOnlineMode(mDevice.isConnected() || mDevice.isBusy(), false);
+        refreshProfiles();
+    }
+
+    /**
+     * Switches between online/offline mode.
+     * 
+     * @param onlineMode Whether to be in online mode, or offline mode.
+     * @param takeAction Whether to take action (i.e., connect or disconnect)
+     *            based on the new online mode.
+     */
+    private void setOnlineMode(boolean onlineMode, boolean takeAction) {
+        mOnlineMode = onlineMode;
+            
+        if (takeAction) {
+            if (onlineMode) {
+                mDevice.connect();
+            } else {
+                mDevice.disconnect();
+            }
+        }
+            
+        refreshOnlineModePreference();
+    }
+    
+    private void refreshOnlineModePreference() {
+        mOnlineModePreference.setChecked(mOnlineMode);
+
+        /* Gray out checkbox while connecting and disconnecting */
+        mOnlineModePreference.setEnabled(!mDevice.isBusy());
+
+        /**
+         * 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) {
+                profilePref = createProfilePreference(profile);
+                mProfileContainer.addPreference(profilePref);
+            } else {
+                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);
+
+        /* Gray out checkbox while connecting and disconnecting */
+        profilePref.setEnabled(!mDevice.isBusy());
+
+        profilePref.setSummary(getProfileSummary(profileManager, profile, address,
+                connectionStatus, mOnlineMode));
+        
+        profilePref.setChecked(profileManager.isPreferred(address));
+    }
+
+    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..a488540
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDevice.java
@@ -0,0 +1,576 @@
+/*
+ * 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.util.Log;
+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 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 bondState = getBondState();
+
+        if (isConnected()) {
+            askDisconnect();
+        } else if (bondState == BluetoothDevice.BOND_BONDED) {
+            connect();
+        } else if (bondState == BluetoothDevice.BOND_NOT_BONDED) {
+            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;
+
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+
+        Context context = mLocalManager.getContext();
+        boolean hasAtLeastOnePreferredProfile = false;
+        for (Profile profile : mProfiles) {
+            LocalBluetoothProfileManager profileManager =
+                    LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+            if (profileManager.isPreferred(mAddress)) {
+                hasAtLeastOnePreferredProfile = true;
+                connectInt(profile);
+            }
+        }
+
+        if (!hasAtLeastOnePreferredProfile) {
+            connectAndPreferAllProfiles();
+        }
+    }
+
+    private void connectAndPreferAllProfiles() {
+        if (!ensurePaired()) return;
+
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+
+        Context context = mLocalManager.getContext();
+        for (Profile profile : mProfiles) {
+            LocalBluetoothProfileManager profileManager =
+                    LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+            profileManager.setPreferred(mAddress, true);
+            connectInt(profile);
+        }
+    }
+
+    public void connect(Profile profile) {
+        // Reset the only-show-one-error-dialog tracking variable
+        mIsConnectingErrorPossible = true;
+        connectInt(profile);
+    }
+
+    public void connectInt(Profile profile) {
+        if (!ensurePaired()) return;
+
+        LocalBluetoothProfileManager profileManager =
+                LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile);
+        int status = profileManager.getConnectionStatus(mAddress);
+        if (!SettingsBtStatus.isConnectionStatusConnected(status)) {
+            if (profileManager.connect(mAddress) != BluetoothDevice.RESULT_SUCCESS) {
+                Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
+            }
+        }
+    }
+
+    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 (getBondState() == BluetoothDevice.BOND_NOT_BONDED) {
+            pair();
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    public void pair() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+        // Pairing is unreliable while scanning, so cancel discovery
+        if (manager.isDiscovering()) {
+            manager.cancelDiscovery();
+        }
+
+        if (!mLocalManager.getBluetoothManager().createBond(mAddress)) {
+            mLocalManager.showError(mAddress, R.string.bluetooth_error_title,
+                    R.string.bluetooth_pairing_error_message);
+        }
+    }
+
+    public void unpair() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+        switch (getBondState()) {
+        case BluetoothDevice.BOND_BONDED:
+            manager.removeBond(mAddress);
+            break;
+
+        case BluetoothDevice.BOND_BONDING:
+            manager.cancelBondProcess(mAddress);
+            break;
+        }
+    }
+
+    private void fillData() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+
+        fetchName();
+        fetchBtClass();
+
+        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 getBondState() {
+        return mLocalManager.getBluetoothManager().getBondState(mAddress);
+    }
+
+    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 (getBondState() == BluetoothDevice.BOND_BONDING) {
+            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;
+        }
+    }
+
+    /**
+     * Fetches a new value for the cached BT class.
+     */
+    private void fetchBtClass() {
+        mBtClass = mLocalManager.getBluetoothManager().getRemoteClass(mAddress);
+        mProfiles.clear();
+        LocalBluetoothProfileManager.fill(mBtClass, mProfiles);
+    }
+
+    /**
+     * Refreshes the UI for the BT class, including fetching the latest value
+     * for the class.
+     */
+    public void refreshBtClass() {
+        fetchBtClass();
+        dispatchAttributesChanged();
+    }
+
+    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);
+            }
+        }
+
+        return SettingsBtStatus.getPairingStatusSummary(getBondState());
+    }
+
+    /**
+     * 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;
+
+        int bondState = getBondState();
+        boolean isConnected = isConnected();
+        boolean hasProfiles = mProfiles.size() > 0;
+
+        menu.setHeaderTitle(getName());
+
+        if (isConnected) {
+            menu.add(0, CONTEXT_ITEM_DISCONNECT, 0, R.string.bluetooth_device_context_disconnect);
+        } else if (hasProfiles) {
+            // For connection action, show either "Connect" or "Pair & connect"
+            int connectString = (bondState == BluetoothDevice.BOND_NOT_BONDED)
+                    ? R.string.bluetooth_device_context_pair_connect
+                    : R.string.bluetooth_device_context_connect;
+            menu.add(0, CONTEXT_ITEM_CONNECT, 0, connectString);
+        }
+
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            // 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:
+                mLocalManager.getBluetoothManager().disconnectRemoteDeviceAcl(mAddress);
+                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.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
+            (getBondState() == BluetoothDevice.BOND_BONDED ? 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..6bb2b4a
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothDeviceManager.java
@@ -0,0 +1,229 @@
+/*
+ * 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 boolean readPairedDevices() {
+        BluetoothDevice manager = mLocalManager.getBluetoothManager();
+        String[] bondedAddresses = manager.listBonds();
+        if (bondedAddresses == null) return false;
+        
+        boolean deviceAdded = false;
+        for (String address : bondedAddresses) {
+            LocalBluetoothDevice device = findDevice(address);
+            if (device == null) {
+                device = new LocalBluetoothDevice(mLocalManager.getContext(), address);
+                mDevices.add(device);
+                dispatchDeviceAdded(device);
+                deviceAdded = true;
+            }
+        }
+        
+        return deviceAdded;
+    }
+    
+    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.getBondState() == BluetoothDevice.BOND_NOT_BONDED &&
+                !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, int bondState) {
+        LocalBluetoothDevice device = findDevice(address);
+        if (device == null) {
+            if (!readPairedDevices()) {
+                Log.e(TAG, "Got bonding state changed for " + address +
+                        ", but we have no record of that device.");
+            }
+            return;
+        }
+
+        device.refresh();
+
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            // Auto-connect after pairing
+            device.connect();
+        }
+    }
+
+    /**
+     * Called when there is a bonding error.
+     * 
+     * @param address The address of the remote device.
+     * @param reason The reason, one of the error reasons from
+     *            BluetoothDevice.UNBOND_REASON_*
+     */
+    public synchronized void onBondingError(String address, int reason) {
+        mLocalManager.showError(address, R.string.bluetooth_error_title,
+                (reason == BluetoothDevice.UNBOND_REASON_AUTH_FAILED) ?
+                        R.string.bluetooth_pairing_pin_error_message :
+                        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);
+        }
+    }
+    
+    public synchronized void onBtClassChanged(String address) {
+        LocalBluetoothDevice device = findDevice(address);
+        if (device != null) {
+            device.refreshBtClass();
+        }
+    }
+}
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
new file mode 100644
index 0000000..4671fac
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
@@ -0,0 +1,279 @@
+/*
+ * 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.BluetoothA2dp;
+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 AlertDialog mErrorDialog = null;
+
+    private BluetoothDevice mManager;
+
+    private LocalBluetoothDeviceManager mLocalDeviceManager;
+    private BluetoothEventRedirector mEventRedirector;
+    private BluetoothA2dp mBluetoothA2dp;
+    
+    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();
+
+        mBluetoothA2dp = new BluetoothA2dp(context);
+
+        return true;
+    }
+    
+    public BluetoothDevice getBluetoothManager() {
+        return mManager;
+    }
+    
+    public Context getContext() {
+        return mContext;
+    }
+
+    public Activity getForegroundActivity() {
+        return mForegroundActivity;
+    }
+    
+    public void setForegroundActivity(Activity activity) {
+        if (mErrorDialog != null) {
+            mErrorDialog.dismiss();
+            mErrorDialog = null;
+        }
+        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 {
+            if (!force) {
+                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
+                // unless forced
+                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
+                    return;
+                }
+
+                // If we are playing music, don't scan unless forced.
+                List<String> sinks = mBluetoothA2dp.listConnectedSinks();
+                if (sinks != null) {
+                    for (String address : sinks) {
+                        if (mBluetoothA2dp.getSinkState(address) == BluetoothA2dp.STATE_PLAYING) {
+                            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 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
+            mErrorDialog = 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..a1a2af6
--- /dev/null
+++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
@@ -0,0 +1,280 @@
+/*
+ * 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 android.os.Handler;
+import android.text.TextUtils;
+
+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;
+    }
+
+    /**
+     * 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 (BluetoothA2dp.doesClassMatchSink(btClass)) {
+            profiles.add(Profile.A2DP);
+        }
+        
+        if (BluetoothHeadset.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 abstract boolean isPreferred(String address);
+
+    public abstract void setPreferred(String address, boolean preferred);
+
+    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());
+        }
+
+        @Override
+        public int connect(String address) {
+            return mService.connectSink(address);
+        }
+
+        @Override
+        public int disconnect(String address) {
+            return mService.disconnectSink(address);
+        }
+        
+        @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);
+            }
+        }
+
+        @Override
+        public boolean isPreferred(String address) {
+            return mService.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF;
+        }
+
+        @Override
+        public void setPreferred(String address, boolean preferred) {
+            mService.setSinkPriority(address,
+                    preferred ? BluetoothA2dp.PRIORITY_AUTO : BluetoothA2dp.PRIORITY_OFF);
+        }
+
+        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
+            implements BluetoothHeadset.ServiceListener {
+        private BluetoothHeadset mService;
+        private Handler mUiHandler = new Handler();
+        
+        public HeadsetProfileManager(LocalBluetoothManager localManager) {
+            super(localManager);
+            mService = new BluetoothHeadset(localManager.getContext(), this);
+        }
+
+        public void onServiceConnected() {
+            // This could be called on a non-UI thread, funnel to UI thread.
+            mUiHandler.post(new Runnable() {
+                public void run() {
+                    /*
+                     * We just bound to the service, so refresh the UI of the
+                     * headset device.
+                     */
+                    String address = mService.getHeadsetAddress();
+                    if (TextUtils.isEmpty(address)) return;
+                    mLocalManager.getLocalDeviceManager().onProfileStateChanged(address);
+                }
+            });
+        }
+
+        public void onServiceDisconnected() {
+        }
+
+        @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)
+                    ? 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;
+            }
+        }
+        
+        @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);
+            }
+        }
+
+        @Override
+        public boolean isPreferred(String address) {
+            return mService.getPriority(address) > BluetoothHeadset.PRIORITY_OFF;
+        }
+
+        @Override
+        public void setPreferred(String address, boolean preferred) {
+            mService.setPriority(address,
+                    preferred ? BluetoothHeadset.PRIORITY_AUTO : BluetoothHeadset.PRIORITY_OFF);
+        }
+
+        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..d2cbef5
--- /dev/null
+++ b/src/com/android/settings/bluetooth/SettingsBtStatus.java
@@ -0,0 +1,81 @@
+/*
+ * 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 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;
+    }
+    
+    public static final int getPairingStatusSummary(int bondState) {
+        switch (bondState) {
+        case BluetoothDevice.BOND_BONDED:
+            return R.string.bluetooth_paired;
+        case BluetoothDevice.BOND_BONDING:
+            return R.string.bluetooth_pairing;
+        case BluetoothDevice.BOND_NOT_BONDED:
+            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
new file mode 100644
index 0000000..75a84b7
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/Memory.java
@@ -0,0 +1,218 @@
+/*
+ * 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.deviceinfo;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+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;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+
+import com.android.settings.R;
+
+import java.io.File;
+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";
+
+    private static final String MEMORY_SD_AVAIL = "memory_sd_avail";
+
+    private static final String MEMORY_SD_UNMOUNT = "memory_sd_unmount";
+
+    private static final String MEMORY_SD_FORMAT = "memory_sd_format";
+    private Resources mRes;
+
+    private Preference mSdSize;
+    private Preference mSdAvail;
+    private Preference mSdUnmount;
+    private Preference mSdFormat;
+    
+    // Access using getMountService()
+    private IMountService mMountService = null;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        
+        addPreferencesFromResource(R.xml.device_info_memory);
+        
+        mRes = getResources();
+        mSdSize = findPreference(MEMORY_SD_SIZE);
+        mSdAvail = findPreference(MEMORY_SD_AVAIL);
+        mSdUnmount = findPreference(MEMORY_SD_UNMOUNT);
+        mSdFormat = findPreference(MEMORY_SD_FORMAT);
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_REMOVED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SHARED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
+        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE);
+        intentFilter.addAction(Intent.ACTION_MEDIA_NOFS);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        intentFilter.addDataScheme("file");
+        registerReceiver(mReceiver, intentFilter);
+
+        updateMemoryStatus();
+    }
+    
+    @Override
+    protected void onPause() {
+        super.onPause();
+        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) {
+            unmount();
+            return true;
+        } else if (preference == mSdFormat) {
+            Intent intent = new Intent(Intent.ACTION_VIEW);
+            intent.setClass(this, com.android.settings.MediaFormat.class);
+            startActivity(intent);
+            return true;
+        }
+        
+        return false;
+    }
+     
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateMemoryStatus();
+        }
+    };
+
+    private void unmount() {
+        IMountService mountService = getMountService();
+        try {
+            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();
+        }
+    }
+
+    private void updateMemoryStatus() {
+        String status = Environment.getExternalStorageState();
+        String readOnly = "";
+        if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
+            status = Environment.MEDIA_MOUNTED;
+            readOnly = mRes.getString(R.string.read_only);
+        }
+ 
+        mSdFormat.setEnabled(false);
+
+        if (status.equals(Environment.MEDIA_MOUNTED)) {
+            try {
+                File path = Environment.getExternalStorageDirectory();
+                StatFs stat = new StatFs(path.getPath());
+                long blockSize = stat.getBlockSize();
+                long totalBlocks = stat.getBlockCount();
+                long availableBlocks = stat.getAvailableBlocks();
+                
+                mSdSize.setSummary(formatSize(totalBlocks * blockSize));
+                mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
+                mSdUnmount.setEnabled(true);
+            } catch (IllegalArgumentException e) {
+                // this can occur if the SD card is removed, but we haven't received the 
+                // ACTION_MEDIA_REMOVED Intent yet.
+                status = Environment.MEDIA_REMOVED;
+            }
+            
+        } else {
+            mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
+            mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
+            mSdUnmount.setEnabled(false);
+
+            if (status.equals(Environment.MEDIA_UNMOUNTED) ||
+                status.equals(Environment.MEDIA_NOFS) ||
+                status.equals(Environment.MEDIA_UNMOUNTABLE) ) {
+                mSdFormat.setEnabled(true);
+            }
+
+            
+        }
+
+        File path = Environment.getDataDirectory();
+        StatFs stat = new StatFs(path.getPath());
+        long blockSize = stat.getBlockSize();
+        long availableBlocks = stat.getAvailableBlocks();
+        findPreference("memory_internal_avail").setSummary(formatSize(availableBlocks * blockSize));
+    }
+    
+    private String formatSize(long size) {
+        String suffix = null;
+        
+        // add KB or MB suffix if size is greater than 1K or 1M
+        if (size >= 1024) {
+            suffix = " KB";
+            size /= 1024;
+            if (size >= 1024) {
+                suffix = " MB";
+                size /= 1024;
+            }
+        }
+        
+        DecimalFormat formatter = new DecimalFormat();
+        formatter.setGroupingSize(3);
+        String result = formatter.format(size);
+                
+        if (suffix != null)
+            result = result + suffix;
+        return result;
+    }
+    
+}
diff --git a/src/com/android/settings/deviceinfo/Status.java b/src/com/android/settings/deviceinfo/Status.java
new file mode 100644
index 0000000..4132ed5
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/Status.java
@@ -0,0 +1,392 @@
+/*
+ * 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.deviceinfo;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.NetStat;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.PhoneStateIntentReceiver;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.settings.R;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Display the following information
+ * # Phone Number
+ * # Network
+ * # Roaming
+ * # IMEI
+ * # Network type
+ * # Signal Strength
+ * # Battery Strength  : TODO
+ * # Uptime
+ * # Awake Time
+ * # XMPP/buzz/tickle status : TODO
+ *
+ */
+public class Status extends PreferenceActivity {
+
+    private static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address";
+    private static final String KEY_BT_ADDRESS = "bt_address";
+    private static final String KEY_NETWORK_TRAFFIC_STATS = "network_traffic_stats";
+    private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 200;
+    private static final int EVENT_SERVICE_STATE_CHANGED = 300;
+
+    private static final int EVENT_UPDATE_STATS = 500;
+
+    private TelephonyManager mTelephonyManager;
+    private Phone mPhone = null;
+    private PhoneStateIntentReceiver mPhoneStateReceiver;
+    private Resources mRes;
+    private Preference mSignalStrength;
+    private Preference mUptime;
+    private Preference mAwakeTime;
+
+    private static String sUnknown;
+    
+    private Preference mBatteryStatus;
+    private Preference mBatteryLevel;
+
+    private Handler mHandler;
+
+    private static class MyHandler extends Handler {
+        private WeakReference<Status> mStatus;
+
+        public MyHandler(Status activity) {
+            mStatus = new WeakReference<Status>(activity);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            Status status = mStatus.get();
+            if (status == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case EVENT_SIGNAL_STRENGTH_CHANGED:
+                    status.updateSignalStrength();
+                    break;
+
+                case EVENT_SERVICE_STATE_CHANGED:
+                    ServiceState serviceState = status.mPhoneStateReceiver.getServiceState();
+                    status.updateServiceState(serviceState);
+                    break;
+
+                case EVENT_UPDATE_STATS:
+                    status.updateTimes();
+                    status.setNetworkTrafficStats();
+                    sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);
+                    break;
+            }
+        }
+    }
+
+    private BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
+        
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+
+                int level = intent.getIntExtra("level", 0);
+                int scale = intent.getIntExtra("scale", 100);
+                
+                mBatteryLevel.setSummary(String.valueOf(level * 100 / scale) + "%");
+                
+                int plugType = intent.getIntExtra("plugged", 0);
+                int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
+                String statusString;
+                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+                    statusString = getString(R.string.battery_info_status_charging);
+                    if (plugType > 0) {
+                        statusString = statusString + " " + getString(
+                                (plugType == BatteryManager.BATTERY_PLUGGED_AC)
+                                        ? R.string.battery_info_status_charging_ac
+                                        : R.string.battery_info_status_charging_usb);
+                    }
+                } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+                    statusString = getString(R.string.battery_info_status_discharging);
+                } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
+                    statusString = getString(R.string.battery_info_status_not_charging);
+                } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
+                    statusString = getString(R.string.battery_info_status_full);
+                } else {
+                    statusString = getString(R.string.battery_info_status_unknown);
+                }
+                mBatteryStatus.setSummary(statusString);
+            }
+        }
+    };
+
+    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onDataConnectionStateChanged(int state) {
+            updateDataState();
+            updateNetworkType();
+        }
+    };
+    
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mHandler = new MyHandler(this);
+        
+        mTelephonyManager = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
+
+        addPreferencesFromResource(R.xml.device_info_status);
+        mBatteryLevel = findPreference("battery_level");
+        mBatteryStatus = findPreference("battery_status");
+        
+        mRes = getResources();
+        if (sUnknown == null) {
+            sUnknown = mRes.getString(R.string.device_info_default);
+        }
+        
+        mPhone = PhoneFactory.getDefaultPhone();
+        mSignalStrength = findPreference("signal_strength");
+        mUptime = findPreference("up_time");
+        mAwakeTime = findPreference("awake_time");
+        
+        setSummaryText("imei", mPhone.getDeviceId());
+        setSummaryText("imei_sv",
+                ((TelephonyManager) getSystemService(TELEPHONY_SERVICE))
+                        .getDeviceSoftwareVersion());
+        setSummaryText("number", mPhone.getLine1Number());
+
+        mPhoneStateReceiver = new PhoneStateIntentReceiver(this, mHandler);
+        mPhoneStateReceiver.notifySignalStrength(EVENT_SIGNAL_STRENGTH_CHANGED);
+        mPhoneStateReceiver.notifyServiceState(EVENT_SERVICE_STATE_CHANGED);
+        
+        setWifiStatus();
+        setBtStatus();
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mPhoneStateReceiver.registerIntent();
+        registerReceiver(mBatteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        
+        updateSignalStrength();
+        updateServiceState(mPhone.getServiceState());
+        updateDataState();
+        
+        mTelephonyManager.listen(mPhoneStateListener,
+                  PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
+
+        mHandler.sendEmptyMessage(EVENT_UPDATE_STATS);
+    }
+    
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        mPhoneStateReceiver.unregisterIntent();
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+        unregisterReceiver(mBatteryInfoReceiver);
+        mHandler.removeMessages(EVENT_UPDATE_STATS);
+    }
+
+    /**
+     * @param preference The key for the Preference item
+     * @param property The system property to fetch
+     * @param alt The default value, if the property doesn't exist
+     */
+    private void setSummary(String preference, String property, String alt) {
+        try {
+            findPreference(preference).setSummary(
+                    SystemProperties.get(property, alt));
+        } catch (RuntimeException e) {
+            
+        }
+    }
+
+    private void setSummaryText(String preference, String text) {
+        if (TextUtils.isEmpty(text)) {
+            text = sUnknown;
+        }
+        
+        findPreference(preference).setSummary(text);
+    }
+    
+    private void updateNetworkType() {
+        // Whether EDGE, UMTS, etc...
+        setSummary("network_type", TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE, sUnknown);
+    }
+    
+    private void updateDataState() {
+        int state = mTelephonyManager.getDataState();
+        String display = mRes.getString(R.string.radioInfo_unknown);
+
+        switch (state) {
+            case TelephonyManager.DATA_CONNECTED:
+                display = mRes.getString(R.string.radioInfo_data_connected);
+                break;
+            case TelephonyManager.DATA_SUSPENDED:
+                display = mRes.getString(R.string.radioInfo_data_suspended);
+                break;
+            case TelephonyManager.DATA_CONNECTING:
+                display = mRes.getString(R.string.radioInfo_data_connecting);
+                break;
+            case TelephonyManager.DATA_DISCONNECTED:
+                display = mRes.getString(R.string.radioInfo_data_disconnected);
+                break;
+        }
+        
+        setSummaryText("data_state", display);
+    }
+
+    private void updateServiceState(ServiceState serviceState) {
+        int state = serviceState.getState();
+        String display = mRes.getString(R.string.radioInfo_unknown);
+        
+        switch (state) {
+            case ServiceState.STATE_IN_SERVICE:
+                display = mRes.getString(R.string.radioInfo_service_in);
+                break;
+            case ServiceState.STATE_OUT_OF_SERVICE:
+            case ServiceState.STATE_EMERGENCY_ONLY:
+                display = mRes.getString(R.string.radioInfo_service_out);
+                break;
+            case ServiceState.STATE_POWER_OFF:
+                display = mRes.getString(R.string.radioInfo_service_off);
+                break;
+        }
+        
+        setSummaryText("service_state", display);
+        
+        if (serviceState.getRoaming()) {
+            setSummaryText("roaming_state", mRes.getString(R.string.radioInfo_roaming_in));
+        } else {
+            setSummaryText("roaming_state", mRes.getString(R.string.radioInfo_roaming_not));
+        }
+        setSummaryText("operator_name", serviceState.getOperatorAlphaLong());
+    }
+    
+    void updateSignalStrength() {
+        int state =
+                mPhoneStateReceiver.getServiceState().getState();
+        Resources r = getResources();
+
+        if ((ServiceState.STATE_OUT_OF_SERVICE == state) ||
+                (ServiceState.STATE_POWER_OFF == state)) {
+            mSignalStrength.setSummary("0");
+        }
+        
+        int signalDbm = mPhoneStateReceiver.getSignalStrengthDbm();
+        
+        if (-1 == signalDbm) signalDbm = 0;
+
+        int signalAsu = mPhoneStateReceiver.getSignalStrength();
+
+        if (-1 == signalAsu) signalAsu = 0;
+
+        mSignalStrength.setSummary(String.valueOf(signalDbm) + " "
+                    + r.getString(R.string.radioInfo_display_dbm) + "   "
+                    + String.valueOf(signalAsu) + " "
+                    + r.getString(R.string.radioInfo_display_asu));
+    }
+    
+    private void setWifiStatus() {
+        WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
+        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+
+        Preference wifiMacAddressPref = findPreference(KEY_WIFI_MAC_ADDRESS);
+        String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
+        wifiMacAddressPref.setSummary(!TextUtils.isEmpty(macAddress) ? macAddress 
+                : getString(R.string.status_unavailable));
+    }
+
+    private void setBtStatus() {
+        BluetoothDevice bluetooth = (BluetoothDevice) getSystemService(BLUETOOTH_SERVICE);
+        Preference btAddressPref = findPreference(KEY_BT_ADDRESS);
+
+        if (bluetooth == null) {
+            // device not BT capable
+            getPreferenceScreen().removePreference(btAddressPref);
+        } else {
+            String address = bluetooth.isEnabled() ? bluetooth.getAddress() : null;
+            btAddressPref.setSummary(!TextUtils.isEmpty(address) ? address
+                    : getString(R.string.status_unavailable));
+        }
+    }
+
+    private void setNetworkTrafficStats() {
+        long txPkts = NetStat.getTotalTxPkts();
+        long txBytes = NetStat.getTotalTxBytes();
+        long rxPkts = NetStat.getTotalRxPkts();
+        long rxBytes = NetStat.getTotalRxBytes();
+
+        Preference netStatsPref = findPreference(KEY_NETWORK_TRAFFIC_STATS);
+        netStatsPref.setSummary(getString(R.string.status_network_traffic_summary,
+                txPkts, txBytes, rxPkts, rxBytes));
+    }
+
+    void updateTimes() {
+        long at = SystemClock.uptimeMillis() / 1000;
+        long ut = SystemClock.elapsedRealtime() / 1000;
+        long st = ut - at;
+
+        if (ut == 0) {
+            ut = 1;
+        }
+
+        mUptime.setSummary(convert(ut));
+        mAwakeTime.setSummary(convert(at) + " (" + (((1000 * at / ut) + 5) / 10) + "%)");
+    }
+    
+    private String pad(int n) {
+        if (n >= 10) {
+            return String.valueOf(n);
+        } else {
+            return "0" + String.valueOf(n);
+        }
+    }
+
+    private String convert(long t) {
+        int s = (int)(t % 60);
+        int m = (int)((t / 60) % 60);
+        int h = (int)((t / 3600));
+
+        return h + ":" + pad(m) + ":" + pad(s);
+    }
+}
diff --git a/src/com/android/settings/quicklaunch/BookmarkPicker.java b/src/com/android/settings/quicklaunch/BookmarkPicker.java
new file mode 100644
index 0000000..7152abb
--- /dev/null
+++ b/src/com/android/settings/quicklaunch/BookmarkPicker.java
@@ -0,0 +1,331 @@
+/*
+ * 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.quicklaunch;
+
+import com.android.settings.R;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Activity to pick a bookmark that will be returned to the caller.
+ * <p>
+ * Currently, bookmarks are either:
+ * <li> Activities that are in the launcher
+ * <li> Activities that are within an app that is capable of being launched with
+ * the {@link Intent#ACTION_CREATE_SHORTCUT}.
+ */
+public class BookmarkPicker extends ListActivity implements SimpleAdapter.ViewBinder {
+
+    private static final String TAG = "BookmarkPicker";
+
+    /** Extra in the returned intent from this activity. */
+    public static final String EXTRA_TITLE = "com.android.settings.quicklaunch.TITLE";
+    
+    /** Extra that should be provided, and will be returned. */
+    public static final String EXTRA_SHORTCUT = "com.android.settings.quicklaunch.SHORTCUT";
+
+    /**
+     * The request code for the screen to create a bookmark that is WITHIN an
+     * application. For example, Gmail can return a bookmark for the inbox
+     * folder.
+     */
+    private static final int REQUEST_CREATE_SHORTCUT = 1;
+
+    /** Intent used to get all the activities that are launch-able */
+    private static Intent sLaunchIntent;
+    /** Intent used to get all the activities that are {@link #REQUEST_CREATE_SHORTCUT}-able */
+    private static Intent sShortcutIntent;
+    
+    /**
+     * List of ResolveInfo for activities that we can bookmark (either directly
+     * to the activity, or by launching the activity and it returning a bookmark
+     * WITHIN that application).
+     */
+    private List<ResolveInfo> mResolveList;
+    
+    // List adapter stuff
+    private static final String KEY_TITLE = "TITLE";
+    private static final String KEY_RESOLVE_INFO = "RESOLVE_INFO";
+    private static final String sKeys[] = new String[] { KEY_TITLE, KEY_RESOLVE_INFO };
+    private static final int sResourceIds[] = new int[] { R.id.title, R.id.icon };
+    private SimpleAdapter mMyAdapter;
+
+    /** Display those activities that are launch-able */
+    private static final int DISPLAY_MODE_LAUNCH = 0;
+    /** Display those activities that are able to have bookmarks WITHIN the application */
+    private static final int DISPLAY_MODE_SHORTCUT = 1;
+    private int mDisplayMode = DISPLAY_MODE_LAUNCH;
+    
+    private Handler mUiHandler = new Handler();
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        updateListAndAdapter();
+    }
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        menu.add(0, DISPLAY_MODE_LAUNCH, 0, R.string.quick_launch_display_mode_applications)
+                .setIcon(com.android.internal.R.drawable.ic_menu_archive);
+        menu.add(0, DISPLAY_MODE_SHORTCUT, 0, R.string.quick_launch_display_mode_shortcuts)
+                .setIcon(com.android.internal.R.drawable.ic_menu_goto);
+        return true;
+    }
+    
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        menu.findItem(DISPLAY_MODE_LAUNCH).setVisible(mDisplayMode != DISPLAY_MODE_LAUNCH);
+        menu.findItem(DISPLAY_MODE_SHORTCUT).setVisible(mDisplayMode != DISPLAY_MODE_SHORTCUT);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        
+        switch (item.getItemId()) {
+
+            case DISPLAY_MODE_LAUNCH: 
+                mDisplayMode = DISPLAY_MODE_LAUNCH;
+                break;
+            
+            case DISPLAY_MODE_SHORTCUT:
+                mDisplayMode = DISPLAY_MODE_SHORTCUT;
+                break;
+            
+            default:
+                return false;
+        }
+        
+        updateListAndAdapter();
+        return true;
+    }
+
+    private void ensureIntents() {
+        if (sLaunchIntent == null) {
+            sLaunchIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
+            sShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+        }
+    }
+
+    /**
+     * This should be called from the UI thread.
+     */
+    private void updateListAndAdapter() {
+        // Get the activities in a separate thread
+        new Thread("data updater") {
+            @Override
+            public void run() {
+                synchronized (BookmarkPicker.this) {
+                    /*
+                     * Don't touch any of the lists that are being used by the
+                     * adapter in this thread!
+                     */
+                    ArrayList<ResolveInfo> newResolveList = new ArrayList<ResolveInfo>();
+                    ArrayList<Map<String, ?>> newAdapterList = new ArrayList<Map<String, ?>>();
+
+                    fillResolveList(newResolveList);
+                    Collections.sort(newResolveList,
+                            new ResolveInfo.DisplayNameComparator(getPackageManager()));
+                    
+                    fillAdapterList(newAdapterList, newResolveList);
+                    
+                    updateAdapterToUseNewLists(newAdapterList, newResolveList);
+                }
+            }
+        }.start();  
+    }
+    
+    private void updateAdapterToUseNewLists(final ArrayList<Map<String, ?>> newAdapterList,
+            final ArrayList<ResolveInfo> newResolveList) {
+        // Post this back on the UI thread
+        mUiHandler.post(new Runnable() {
+            public void run() {
+                /*
+                 * SimpleAdapter does not support changing the lists after it
+                 * has been created. We just create a new instance.
+                 */
+                mMyAdapter = createResolveAdapter(newAdapterList);
+                mResolveList = newResolveList;
+                setListAdapter(mMyAdapter);
+            }
+        });
+    }
+    
+    /**
+     * Gets all activities matching our current display mode.
+     * 
+     * @param list The list to fill.
+     */
+    private void fillResolveList(List<ResolveInfo> list) {
+        ensureIntents();
+        PackageManager pm = getPackageManager();
+        list.clear();
+        
+        if (mDisplayMode == DISPLAY_MODE_LAUNCH) {
+            list.addAll(pm.queryIntentActivities(sLaunchIntent, 0));
+        } else if (mDisplayMode == DISPLAY_MODE_SHORTCUT) {
+            list.addAll(pm.queryIntentActivities(sShortcutIntent, 0)); 
+        }
+    }
+    
+    private SimpleAdapter createResolveAdapter(List<Map<String, ?>> list) {
+        SimpleAdapter adapter = new SimpleAdapter(this, list,
+                R.layout.bookmark_picker_item, sKeys, sResourceIds);
+        adapter.setViewBinder(this);
+        return adapter;
+    }
+
+    private void fillAdapterList(List<Map<String, ?>> list,
+            List<ResolveInfo> resolveList) {
+        list.clear();
+        int resolveListSize = resolveList.size();
+        for (int i = 0; i < resolveListSize; i++) {
+            ResolveInfo info = resolveList.get(i);
+            /*
+             * Simple adapter craziness. For each item, we need to create a map
+             * from a key to its value (the value can be any object--the view
+             * binder will take care of filling the View with a representation
+             * of that object).
+             */
+            Map<String, Object> map = new TreeMap<String, Object>();
+            map.put(KEY_TITLE, getResolveInfoTitle(info));
+            map.put(KEY_RESOLVE_INFO, info);
+            list.add(map);
+        }
+    }
+
+    /** Get the title for a resolve info. */
+    private String getResolveInfoTitle(ResolveInfo info) {
+        CharSequence label = info.loadLabel(getPackageManager());
+        if (label == null) label = info.activityInfo.name;
+        return label != null ? label.toString() : null;
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        if (position >= mResolveList.size()) return;
+
+        ResolveInfo info = mResolveList.get(position);
+        
+        switch (mDisplayMode) {
+
+            case DISPLAY_MODE_LAUNCH: 
+                // We can go ahead and return the clicked info's intent
+                Intent intent = getIntentForResolveInfo(info, Intent.ACTION_MAIN);
+                intent.addCategory(Intent.CATEGORY_LAUNCHER);
+                finish(intent, getResolveInfoTitle(info));
+                break;
+
+            case DISPLAY_MODE_SHORTCUT:
+                // Start the shortcut activity so the user can pick the actual intent
+                // (example: Gmail's shortcut activity shows a list of mailboxes)
+                startShortcutActivity(info);
+                break;
+        }
+        
+    }
+    
+    private static Intent getIntentForResolveInfo(ResolveInfo info, String action) {
+        Intent intent = new Intent(action);
+        ActivityInfo ai = info.activityInfo;
+        intent.setClassName(ai.packageName, ai.name);
+        return intent;
+    }
+
+    /**
+     * Starts an activity to get a shortcut.
+     * <p>
+     * For example, Gmail has an activity that lists the available labels. It
+     * returns a shortcut intent for going directly to this label.
+     */
+    private void startShortcutActivity(ResolveInfo info) {
+        Intent intent = getIntentForResolveInfo(info, Intent.ACTION_CREATE_SHORTCUT);
+        startActivityForResult(intent, REQUEST_CREATE_SHORTCUT);
+        
+        // Will get a callback to onActivityResult
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode != RESULT_OK) {
+            return;
+        }
+        
+        switch (requestCode) {
+            
+            case REQUEST_CREATE_SHORTCUT:
+                if (data != null) {
+                    finish((Intent) data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT),
+                            data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME));
+                }
+                break;
+                
+            default:
+                super.onActivityResult(requestCode, resultCode, data);
+                break;
+        }
+    }
+    
+    /**
+     * Finishes the activity and returns the given data.
+     */
+    private void finish(Intent intent, String title) {
+        // Give back what was given to us (it will have the shortcut, for example)
+        intent.putExtras(getIntent());
+        // Put our information
+        intent.putExtra(EXTRA_TITLE, title);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean setViewValue(View view, Object data, String textRepresentation) {
+        if (view.getId() == R.id.icon) {
+            Drawable icon = ((ResolveInfo) data).loadIcon(getPackageManager());
+            if (icon != null) {
+                ((ImageView) view).setImageDrawable(icon);
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+    
+}
diff --git a/src/com/android/settings/quicklaunch/QuickLaunchSettings.java b/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
new file mode 100644
index 0000000..4d44524
--- /dev/null
+++ b/src/com/android/settings/quicklaunch/QuickLaunchSettings.java
@@ -0,0 +1,347 @@
+/*
+ * 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.quicklaunch;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Handler;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.provider.Settings.Bookmarks;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.AdapterView;
+
+import com.android.settings.R;
+
+/**
+ * Settings activity for quick launch.
+ * <p>
+ * Shows a list of possible shortcuts, the current application each is bound to,
+ * and allows choosing a new bookmark for a shortcut.
+ */
+public class QuickLaunchSettings extends PreferenceActivity implements
+        AdapterView.OnItemLongClickListener, DialogInterface.OnClickListener {
+
+    private static final String TAG = "QuickLaunchSettings";
+
+    private static final String KEY_SHORTCUT_CATEGORY = "shortcut_category";
+
+    private static final int DIALOG_CLEAR_SHORTCUT = 0;
+
+    private static final int REQUEST_PICK_BOOKMARK = 1;
+
+    private static final int COLUMN_SHORTCUT = 0;
+    private static final int COLUMN_TITLE = 1;
+    private static final int COLUMN_INTENT = 2;
+    private static final String[] sProjection = new String[] {
+            Bookmarks.SHORTCUT, Bookmarks.TITLE, Bookmarks.INTENT
+    };
+    private static final String sShortcutSelection = Bookmarks.SHORTCUT + "=?";
+    
+    private Handler mUiHandler = new Handler();
+    
+    private static final String DEFAULT_BOOKMARK_FOLDER = "@quicklaunch";
+    /** Cursor for Bookmarks provider. */
+    private Cursor mBookmarksCursor;
+    /** Listens for changes to Bookmarks provider. */
+    private BookmarksObserver mBookmarksObserver;
+    /** Used to keep track of which shortcuts have bookmarks. */
+    private SparseBooleanArray mBookmarkedShortcuts;
+    
+    /** Preference category to hold the shortcut preferences. */
+    private PreferenceGroup mShortcutGroup;
+    /** Mapping of a shortcut to its preference. */
+    private SparseArray<ShortcutPreference> mShortcutToPreference;
+
+    /** The bookmark title of the shortcut that is being cleared. */
+    private CharSequence mClearDialogBookmarkTitle;
+    private static final String CLEAR_DIALOG_BOOKMARK_TITLE = "CLEAR_DIALOG_BOOKMARK_TITLE";
+    /** The shortcut that is being cleared. */
+    private char mClearDialogShortcut;
+    private static final String CLEAR_DIALOG_SHORTCUT = "CLEAR_DIALOG_SHORTCUT";
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        addPreferencesFromResource(R.xml.quick_launch_settings);
+        
+        mShortcutGroup = (PreferenceGroup) findPreference(KEY_SHORTCUT_CATEGORY);
+        mShortcutToPreference = new SparseArray<ShortcutPreference>();
+        mBookmarksObserver = new BookmarksObserver(mUiHandler);
+        initShortcutPreferences();
+        mBookmarksCursor = managedQuery(Bookmarks.CONTENT_URI, sProjection, null, null);
+        getListView().setOnItemLongClickListener(this);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        getContentResolver().registerContentObserver(Bookmarks.CONTENT_URI, true,
+                mBookmarksObserver);
+        refreshShortcuts();
+    }
+    
+    @Override
+    protected void onPause() {
+        super.onPause();
+        getContentResolver().unregisterContentObserver(mBookmarksObserver);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        
+        // Restore the clear dialog's info
+        mClearDialogBookmarkTitle = state.getString(CLEAR_DIALOG_BOOKMARK_TITLE);
+        mClearDialogShortcut = (char) state.getInt(CLEAR_DIALOG_SHORTCUT, 0);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        
+        // Save the clear dialog's info
+        outState.putCharSequence(CLEAR_DIALOG_BOOKMARK_TITLE, mClearDialogBookmarkTitle);
+        outState.putInt(CLEAR_DIALOG_SHORTCUT, mClearDialogShortcut);
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        switch (id) {
+            
+            case DIALOG_CLEAR_SHORTCUT: {
+                // Create the dialog for clearing a shortcut
+                return new AlertDialog.Builder(this)
+                        .setTitle(getString(R.string.quick_launch_clear_dialog_title))
+                        .setIcon(android.R.drawable.ic_dialog_alert)
+                        .setMessage(getString(R.string.quick_launch_clear_dialog_message,
+                                mClearDialogShortcut, mClearDialogBookmarkTitle))
+                        .setPositiveButton(R.string.quick_launch_clear_ok_button, this)
+                        .setNegativeButton(R.string.quick_launch_clear_cancel_button, this)
+                        .create();
+            }
+        }
+        
+        return super.onCreateDialog(id);
+    }
+    
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        switch (id) {
+            
+            case DIALOG_CLEAR_SHORTCUT: {
+                AlertDialog alertDialog = (AlertDialog) dialog;
+                alertDialog.setMessage(getString(R.string.quick_launch_clear_dialog_message,
+                        mClearDialogShortcut, mClearDialogBookmarkTitle));
+            }
+        }
+    }
+
+    private void showClearDialog(ShortcutPreference pref) {
+
+        if (!pref.hasBookmark()) return;
+        
+        mClearDialogBookmarkTitle = pref.getTitle();
+        mClearDialogShortcut = pref.getShortcut();
+        showDialog(DIALOG_CLEAR_SHORTCUT);
+    }
+    
+    public void onClick(DialogInterface dialog, int which) {
+        if (mClearDialogShortcut > 0 && which == AlertDialog.BUTTON1) {
+            // Clear the shortcut
+            clearShortcut(mClearDialogShortcut);
+        }
+        mClearDialogBookmarkTitle = null;
+        mClearDialogShortcut = 0;
+    }
+
+    private void clearShortcut(char shortcut) {
+        getContentResolver().delete(Bookmarks.CONTENT_URI, sShortcutSelection,
+                new String[] { String.valueOf((int) shortcut) });
+    }
+    
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        if (!(preference instanceof ShortcutPreference)) return false;
+
+        // Open the screen to pick a bookmark for this shortcut
+        ShortcutPreference pref = (ShortcutPreference) preference;
+        Intent intent = new Intent(this, BookmarkPicker.class);
+        intent.putExtra(BookmarkPicker.EXTRA_SHORTCUT, pref.getShortcut());
+        startActivityForResult(intent, REQUEST_PICK_BOOKMARK);
+        
+        return true;
+    }
+
+    public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
+        
+        // Open the clear shortcut dialog
+        Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(position);
+        if (!(pref instanceof ShortcutPreference)) return false;
+        showClearDialog((ShortcutPreference) pref);
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode != RESULT_OK) {
+            return;
+        }
+
+        if (requestCode == REQUEST_PICK_BOOKMARK) {
+            
+            // Returned from the 'pick bookmark for this shortcut' screen
+            if (data == null) {
+                Log.w(TAG, "Result from bookmark picker does not have an intent.");
+                return;
+            }
+            
+            String title = data.getStringExtra(BookmarkPicker.EXTRA_TITLE);
+            char shortcut = data.getCharExtra(BookmarkPicker.EXTRA_SHORTCUT, (char) 0);
+            updateShortcut(shortcut, data);
+            
+        } else {
+            super.onActivityResult(requestCode, resultCode, data);
+        }
+    }
+
+    private void updateShortcut(char shortcut, Intent intent) {
+        // Update the bookmark for a shortcut
+        // Pass an empty title so it gets resolved each time this bookmark is
+        // displayed (since the locale could change after we insert into the provider).
+        Bookmarks.add(getContentResolver(), intent, "", DEFAULT_BOOKMARK_FOLDER, shortcut, 0);
+    }
+    
+    private ShortcutPreference getOrCreatePreference(char shortcut) {
+        ShortcutPreference pref = mShortcutToPreference.get(shortcut);
+        if (pref != null) {
+            return pref;
+        } else {
+            Log.w(TAG, "Unknown shortcut '" + shortcut + "', creating preference anyway");
+            return createPreference(shortcut);
+        }
+    }
+    
+    private ShortcutPreference createPreference(char shortcut) {
+        ShortcutPreference pref = new ShortcutPreference(QuickLaunchSettings.this, shortcut);
+        mShortcutGroup.addPreference(pref);
+        mShortcutToPreference.put(shortcut, pref);
+        return pref;
+    }
+
+    private void initShortcutPreferences() {
+        
+        /** Whether the shortcut has been seen already.  The array index is the shortcut. */
+        SparseBooleanArray shortcutSeen = new SparseBooleanArray();
+        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.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;
+            // TODO: need a to tell if the current keyboard can produce this key code, for now
+            // only allow the letter or digits
+            if (!Character.isLetterOrDigit(shortcut)) continue;
+            shortcutSeen.put(shortcut, true);
+            
+            createPreference(shortcut);
+        }
+    }
+    
+    private synchronized void refreshShortcuts() {
+        Cursor c = mBookmarksCursor;
+        if (c == null) {
+            // Haven't finished querying yet
+            return;
+        }
+        
+        if (!c.requery()) {
+            Log.e(TAG, "Could not requery cursor when refreshing shortcuts.");
+            return;
+        }
+        
+        /**
+         * We use the previous bookmarked shortcuts array to filter out those
+         * shortcuts that had bookmarks before this method call, and don't after
+         * (so we can set the preferences to be without bookmarks).
+         */
+        SparseBooleanArray noLongerBookmarkedShortcuts = mBookmarkedShortcuts;
+        SparseBooleanArray newBookmarkedShortcuts = new SparseBooleanArray(); 
+        while (c.moveToNext()) {
+            char shortcut = Character.toLowerCase((char) c.getInt(COLUMN_SHORTCUT));
+            if (shortcut == 0) continue;
+            
+            ShortcutPreference pref = getOrCreatePreference(shortcut);
+            pref.setTitle(Bookmarks.getTitle(this, c));
+            pref.setSummary(getString(R.string.quick_launch_shortcut,
+                    String.valueOf(shortcut)));
+            pref.setHasBookmark(true);
+            
+            newBookmarkedShortcuts.put(shortcut, true);
+            if (noLongerBookmarkedShortcuts != null) {
+                // After this loop, the shortcuts with value true in this array
+                // will no longer have bookmarks
+                noLongerBookmarkedShortcuts.put(shortcut, false);
+            }
+        }
+        
+        if (noLongerBookmarkedShortcuts != null) {
+            for (int i = noLongerBookmarkedShortcuts.size() - 1; i >= 0; i--) {
+                if (noLongerBookmarkedShortcuts.valueAt(i)) {
+                    // True, so there is no longer a bookmark for this shortcut
+                    char shortcut = (char) noLongerBookmarkedShortcuts.keyAt(i);
+                    ShortcutPreference pref = mShortcutToPreference.get(shortcut);
+                    if (pref != null) {
+                        pref.setHasBookmark(false);
+                    }
+                }
+            }
+        }
+        
+        mBookmarkedShortcuts = newBookmarkedShortcuts;
+        
+        c.deactivate();
+    }
+
+    private class BookmarksObserver extends ContentObserver {
+
+        public BookmarksObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            
+            refreshShortcuts();
+        }
+    }
+}
diff --git a/src/com/android/settings/quicklaunch/ShortcutPreference.java b/src/com/android/settings/quicklaunch/ShortcutPreference.java
new file mode 100644
index 0000000..92efdeb
--- /dev/null
+++ b/src/com/android/settings/quicklaunch/ShortcutPreference.java
@@ -0,0 +1,151 @@
+/*
+ * 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.quicklaunch;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.preference.Preference;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Preference type for a shortcut in {@link QuickLaunchSettings}.
+ */
+public class ShortcutPreference extends Preference implements Comparable<Preference> {
+
+    private static Object sStaticVarsLock = new Object();
+    
+    // These static fields are used across all instances of ShortcutPreference.
+    // There will be many ShortcutPreference instances (~36 for US).
+    private static String STRING_ASSIGN_APPLICATION;
+    private static String STRING_NO_SHORTCUT;
+
+    private static int sDimAlpha;
+    private static ColorStateList sRegularTitleColor;
+    private static ColorStateList sDimTitleColor;
+    private static ColorStateList sRegularSummaryColor;
+    private static ColorStateList sDimSummaryColor;
+    
+    private char mShortcut;
+    private boolean mHasBookmark;
+    
+    public ShortcutPreference(Context context, char shortcut) {
+        super(context);
+
+        synchronized (sStaticVarsLock) {
+            // Init statics. This should only happen for the first ShortcutPreference created,
+            // the rest will already have them initialized.
+            if (STRING_ASSIGN_APPLICATION == null) {
+                STRING_ASSIGN_APPLICATION = context.getString(R.string.quick_launch_assign_application);
+                STRING_NO_SHORTCUT = context.getString(R.string.quick_launch_no_shortcut);
+    
+                TypedValue outValue = new TypedValue();
+                context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
+                sDimAlpha = (int) (outValue.getFloat() * 255);
+            }
+        }
+        
+        mShortcut = shortcut;
+
+        setWidgetLayoutResource(R.layout.preference_widget_shortcut);
+    }
+
+    public char getShortcut() {
+        return mShortcut;
+    }
+
+    public void setShortcut(char shortcut) {
+        if (shortcut != mShortcut) {
+            mShortcut = shortcut;
+            notifyChanged();
+        }
+    }
+
+    public boolean hasBookmark() {
+        return mHasBookmark;
+    }
+
+    public void setHasBookmark(boolean hasBookmark) {
+        if (hasBookmark != mHasBookmark) {
+            mHasBookmark = hasBookmark;
+            notifyChanged();
+        }
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mHasBookmark ? super.getTitle() : STRING_ASSIGN_APPLICATION;
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return mHasBookmark ? super.getSummary() : STRING_NO_SHORTCUT;
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+        
+        TextView shortcutView = (TextView) view.findViewById(R.id.shortcut);
+        if (shortcutView != null) {
+            shortcutView.setText(String.valueOf(mShortcut));
+        }
+    
+        TextView titleView = (TextView) view.findViewById(android.R.id.title);
+
+        synchronized (sStaticVarsLock) {
+            if (sRegularTitleColor == null) {
+                sRegularTitleColor = titleView.getTextColors();
+                sDimTitleColor = sRegularTitleColor.withAlpha(sDimAlpha);
+            }
+        }
+        
+        ColorStateList color = mHasBookmark ? sRegularTitleColor : sDimTitleColor;
+        if (color != null) {
+            titleView.setTextColor(color);
+        }
+        
+        TextView summaryView = (TextView) view.findViewById(android.R.id.summary);
+
+        synchronized (sStaticVarsLock) {
+            if (sRegularSummaryColor == null) {
+                sRegularSummaryColor = summaryView.getTextColors();
+                sDimSummaryColor = sRegularSummaryColor.withAlpha(sDimAlpha);
+            }
+        }
+        
+        color = mHasBookmark ? sRegularSummaryColor : sDimSummaryColor;
+        if (color != null) {
+            summaryView.setTextColor(color);
+        }
+        
+    }
+
+    public int compareTo(Preference another) {
+        if (!(another instanceof ShortcutPreference)) return super.compareTo(another);
+
+        // Letters before digits
+        char other = ((ShortcutPreference) another).mShortcut;
+        if (Character.isDigit(mShortcut) && Character.isLetter(other)) return 1;
+        else if (Character.isDigit(other) && Character.isLetter(mShortcut)) return -1;
+        else return mShortcut - other;
+    }
+    
+}
diff --git a/src/com/android/settings/wifi/AccessPointDialog.java b/src/com/android/settings/wifi/AccessPointDialog.java
new file mode 100644
index 0000000..917ed96
--- /dev/null
+++ b/src/com/android/settings/wifi/AccessPointDialog.java
@@ -0,0 +1,600 @@
+/*
+ * 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.wifi;
+
+import com.android.settings.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.wifi.WifiInfo;
+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;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.TableLayout;
+import android.widget.TextView;
+
+public class AccessPointDialog extends AlertDialog implements DialogInterface.OnClickListener,
+        AdapterView.OnItemSelectedListener, View.OnClickListener {
+
+    private static final String TAG = "AccessPointDialog";
+    private static final String INSTANCE_KEY_ACCESS_POINT_STATE =
+            "com.android.settings.wifi.AccessPointDialog:accessPointState";
+    private static final String INSTANCE_KEY_MODE =
+            "com.android.settings.wifi.AccessPointDialog:mode";
+    private static final String INSTANCE_KEY_CUSTOM_TITLE =
+            "com.android.settings.wifi.AccessPointDialog:customTitle";
+    private static final String INSTANCE_KEY_AUTO_SECURITY_ALLOWED =
+            "com.android.settings.wifi.AccessPointDialog:autoSecurityAllowed";
+    
+    private static final int POSITIVE_BUTTON = BUTTON1;
+    private static final int NEGATIVE_BUTTON = BUTTON2;
+    private static final int NEUTRAL_BUTTON = BUTTON3;
+    
+    /** The dialog should show info connectivity functionality */
+    public static final int MODE_INFO = 0;
+    /** The dialog should configure the detailed AP properties */
+    public static final int MODE_CONFIGURE = 1;
+    /** The dialog should have the password field and connect/cancel */
+    public static final int MODE_RETRY_PASSWORD = 2;
+    
+    // These should be matched with the XML. Both arrays in XML depend on this
+    // ordering!
+    private static final int SECURITY_AUTO = 0;
+    private static final int SECURITY_NONE = 1;
+    private static final int SECURITY_WEP = 2;
+    private static final int SECURITY_WPA_PERSONAL = 3;
+    private static final int SECURITY_WPA2_PERSONAL = 4;
+
+    private static final int[] WEP_TYPE_VALUES = {
+            AccessPointState.WEP_PASSWORD_AUTO, AccessPointState.WEP_PASSWORD_ASCII,
+            AccessPointState.WEP_PASSWORD_HEX
+    };
+    
+    // Button positions, default to impossible values
+    private int mConnectButtonPos = Integer.MAX_VALUE; 
+    private int mForgetButtonPos = Integer.MAX_VALUE;
+    private int mSaveButtonPos = Integer.MAX_VALUE;
+
+    // Client configurable items. Generally, these should be saved in instance state
+    private int mMode = MODE_INFO;
+    private boolean mAutoSecurityAllowed = true;
+    private CharSequence mCustomTitle;
+    // This does not need to be saved in instance state.
+    private WifiLayer mWifiLayer;
+    private AccessPointState mState;
+    
+    // General views
+    private View mView;
+    private TextView mPasswordText;
+    private EditText mPasswordEdit;
+    private CheckBox mShowPasswordCheckBox;
+    
+    // Info-specific views
+    private ViewGroup mTable;
+    
+    // Configure-specific views
+    private EditText mSsidEdit;
+    private Spinner mSecuritySpinner;
+    private Spinner mWepTypeSpinner;
+    
+    public AccessPointDialog(Context context, WifiLayer wifiLayer) {
+        super(context);
+
+        mWifiLayer = wifiLayer;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        onLayout();
+        onFill();
+
+        super.onCreate(savedInstanceState);
+    }
+    
+    @Override
+    public void onRestoreInstanceState(Bundle savedInstanceState) {
+        // Set to a class loader that can find AccessPointState
+        savedInstanceState.setClassLoader(getClass().getClassLoader());
+        
+        mState = savedInstanceState.getParcelable(INSTANCE_KEY_ACCESS_POINT_STATE);
+        mState.setContext(getContext());
+        
+        mMode = savedInstanceState.getInt(INSTANCE_KEY_MODE, mMode);
+        mAutoSecurityAllowed = savedInstanceState.getBoolean(INSTANCE_KEY_AUTO_SECURITY_ALLOWED,
+                mAutoSecurityAllowed);
+        mCustomTitle = savedInstanceState.getCharSequence(INSTANCE_KEY_CUSTOM_TITLE);
+        if (mCustomTitle != null) {
+            setTitle(mCustomTitle);
+        }
+
+        // This is called last since it depends on the above values 
+        super.onRestoreInstanceState(savedInstanceState);
+        
+        if (mShowPasswordCheckBox != null) {
+            // Restore the show-password-state on the edit text
+            setShowPassword(mShowPasswordCheckBox.isChecked());
+        }
+    }
+
+    @Override
+    public Bundle onSaveInstanceState() {
+        Bundle bundle = super.onSaveInstanceState();
+        bundle.putParcelable(INSTANCE_KEY_ACCESS_POINT_STATE, mState);
+        bundle.putInt(INSTANCE_KEY_MODE, mMode);
+        bundle.putBoolean(INSTANCE_KEY_AUTO_SECURITY_ALLOWED, mAutoSecurityAllowed);
+        bundle.putCharSequence(INSTANCE_KEY_CUSTOM_TITLE, mCustomTitle);
+        return bundle;
+    }
+
+    /**
+     * Sets state to show in this dialog.
+     * 
+     * @param state The state.
+     */
+    public void setState(AccessPointState state) {
+        mState = state;
+    }
+
+    /**
+     * Sets the dialog mode.
+     * @param mode One of {@link #MODE_CONFIGURE} or {@link #MODE_INFO}
+     */
+    public void setMode(int mode) {
+        mMode = mode;
+    }
+
+    public void setAutoSecurityAllowed(boolean autoSecurityAllowed) {
+        mAutoSecurityAllowed = autoSecurityAllowed;
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        super.setTitle(title);
+        mCustomTitle = title;
+    }
+
+    @Override
+    public void setTitle(int titleId) {
+        setTitle(getContext().getString(titleId));
+    }
+
+    /** Called after flags are set, the dialog's layout/etc should be set up here */
+    private void onLayout() {
+        final Context context = getContext();
+        final String ssid = mState.getHumanReadableSsid();
+        
+        int positiveButtonResId = 0;
+        int negativeButtonResId = R.string.cancel;
+        int neutralButtonResId = 0;
+
+        if (mCustomTitle == null) {
+            // Generic title is the SSID
+            // We don't want to trigger this as a custom title, so call super's
+            super.setTitle(ssid);
+        }
+        setInverseBackgroundForced(true);
+
+        boolean defaultPasswordVisibility = true;
+        
+        if (mMode == MODE_CONFIGURE) {
+            setLayout(R.layout.wifi_ap_configure);
+
+            positiveButtonResId = R.string.wifi_save_config;
+            mSaveButtonPos = POSITIVE_BUTTON;
+            
+        } else if (mMode == MODE_INFO) {
+            setLayout(R.layout.wifi_ap_info);
+
+            if (mState.isConnectable()) {
+                if (mCustomTitle == null) {
+                    // We don't want to trigger this as a custom title, so call super's
+                    super.setTitle(context.getString(R.string.connect_to_blank, ssid));
+                }
+                positiveButtonResId = R.string.connect;
+                mConnectButtonPos = POSITIVE_BUTTON;
+            }
+
+            if (mState.isForgetable()) {
+                if (positiveButtonResId == 0) {
+                    positiveButtonResId = R.string.forget_network;
+                    mForgetButtonPos = POSITIVE_BUTTON;
+                } else {
+                    neutralButtonResId = R.string.forget_network;
+                    mForgetButtonPos = NEUTRAL_BUTTON;
+                }
+            }
+        } else if (mMode == MODE_RETRY_PASSWORD) {
+            setLayout(R.layout.wifi_ap_retry_password);
+            
+            positiveButtonResId = R.string.connect;
+            mConnectButtonPos = POSITIVE_BUTTON;
+            
+            setGenericPasswordVisible(true);
+            defaultPasswordVisibility = false;
+        }
+
+        if (defaultPasswordVisibility) {
+            if (!mState.configured && mState.seen && mState.hasSecurity()) {
+                setGenericPasswordVisible(true);
+            } else {
+                setGenericPasswordVisible(false);
+            }
+        }
+        
+        setButtons(positiveButtonResId, negativeButtonResId, neutralButtonResId);
+    }
+
+    /** Called when we need to set our member variables to point to the views. */
+    private void onReferenceViews(View view) {
+        mPasswordText = (TextView) view.findViewById(R.id.password_text);
+        mPasswordEdit = (EditText) view.findViewById(R.id.password_edit);
+        
+        mShowPasswordCheckBox = (CheckBox) view.findViewById(R.id.show_password_checkbox);
+        if (mShowPasswordCheckBox != null) {
+            mShowPasswordCheckBox.setOnClickListener(this);
+        }
+        
+        if (mMode == MODE_CONFIGURE) {
+            mSsidEdit = (EditText) view.findViewById(R.id.ssid_edit);
+            mSecuritySpinner = (Spinner) view.findViewById(R.id.security_spinner);
+            mSecuritySpinner.setOnItemSelectedListener(this);
+            setSecuritySpinnerAdapter();
+            mWepTypeSpinner = (Spinner) view.findViewById(R.id.wep_type_spinner);
+            
+        } else if (mMode == MODE_INFO) {
+            mTable = (ViewGroup) view.findViewById(R.id.table);
+        }
+        
+    }
+    
+    private void setSecuritySpinnerAdapter() {
+        Context context = getContext();
+        int arrayResId = mAutoSecurityAllowed ? R.array.wifi_security_entries
+                : R.array.wifi_security_without_auto_entries;         
+
+        ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(context,
+                android.R.layout.simple_spinner_item,
+                context.getResources().getStringArray(arrayResId));
+        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        mSecuritySpinner.setAdapter(adapter);
+    }
+    
+    /** Called when the widgets are in-place waiting to be filled with data */
+    private void onFill() {
+
+        // Appears in the order added
+        if (mMode == MODE_INFO) {
+            if (mState.primary) {
+                addInfoRow(R.string.wifi_status, mState.getSummarizedStatus());
+                addInfoRow(R.string.wifi_link_speed, mState.linkSpeed + WifiInfo.LINK_SPEED_UNITS);
+            }
+    
+            if (mState.seen) {
+                addInfoRow(R.string.signal, getSignalResId(mState.signal));
+            }
+            
+            if (mState.security != null) {
+                addInfoRow(R.string.security, mState.getHumanReadableSecurity());
+            }
+    
+            if (mState.primary && mState.ipAddress != 0) {
+                addInfoRow(R.string.ip_address, Formatter.formatIpAddress(mState.ipAddress));
+            }
+            
+        } else if (mMode == MODE_CONFIGURE) {
+            String ssid = mState.getHumanReadableSsid();
+            if (!TextUtils.isEmpty(ssid)) {
+                mSsidEdit.setText(ssid);
+            }
+            
+            mPasswordEdit.setHint(R.string.wifi_password_unchanged);
+        }
+
+        updatePasswordCaption(mState.security);
+    }
+
+    private void updatePasswordCaption(String security) {
+        
+        if (mPasswordText != null && security != null
+                && security.equals(AccessPointState.WEP)) {
+            mPasswordText.setText(R.string.please_type_hex_key);
+        } else {
+            mPasswordText.setText(R.string.please_type_passphrase);
+        }
+    }
+    
+    private void addInfoRow(int nameResId, String value) {
+        View rowView = getLayoutInflater().inflate(R.layout.wifi_ap_info_row, mTable, false);
+        ((TextView) rowView.findViewById(R.id.name)).setText(nameResId);
+        ((TextView) rowView.findViewById(R.id.value)).setText(value);
+        mTable.addView(rowView);
+    }
+        
+    private void addInfoRow(int nameResId, int valueResId) {
+        addInfoRow(nameResId, getContext().getString(valueResId));
+    }
+    
+    private void setButtons(int positiveResId, int negativeResId, int neutralResId) {
+        final Context context = getContext();
+        
+        if (positiveResId > 0) {
+            setButton(context.getString(positiveResId), this);
+        }
+        
+        if (negativeResId > 0) {
+            setButton2(context.getString(negativeResId), this);
+        }
+
+        if (neutralResId > 0) {
+            setButton3(context.getString(neutralResId), this);
+        }
+    }
+    
+    private void setLayout(int layoutResId) {
+        setView(mView = getLayoutInflater().inflate(layoutResId, null));
+        onReferenceViews(mView);
+    }
+    
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == mForgetButtonPos) {
+            handleForget();
+        } else if (which == mConnectButtonPos) {
+            handleConnect();
+        } else if (which == mSaveButtonPos) {
+            handleSave();
+        }
+    }
+    
+    private void handleForget() {
+        if (!replaceStateWithWifiLayerInstance()) return;
+        mWifiLayer.forgetNetwork(mState);
+    }
+    
+    private void handleConnect() {
+        if (!replaceStateWithWifiLayerInstance()) {
+            Log.w(TAG, "Assuming connecting to a new network.");
+        }
+        
+        /*
+         * If the network is secured and they haven't entered a password, popup
+         * an error. Allow empty passwords if the state already has a password
+         * set (since in that scenario, an empty password means keep the old
+         * password).
+         */
+        String password = getEnteredPassword();
+        boolean passwordIsEmpty = TextUtils.isEmpty(password);
+        
+        /*
+         * When 'retry password', they can not enter a blank password. In any
+         * other mode, we let them enter a blank password if the state already
+         * has a password.
+         */
+        if (passwordIsEmpty && (!mState.hasPassword() || mMode == MODE_RETRY_PASSWORD)
+                && (mState.security != null) && !mState.security.equals(AccessPointState.OPEN)) {
+            new AlertDialog.Builder(getContext())
+                    .setTitle(R.string.error_title)
+                    .setIcon(android.R.drawable.ic_dialog_alert)
+                    .setMessage(R.string.wifi_password_incorrect_error)
+                    .setPositiveButton(android.R.string.ok, null)
+                    .show();
+            return;
+        }
+        
+        if (!passwordIsEmpty) { 
+            mState.setPassword(password);
+        }
+        
+        mWifiLayer.connectToNetwork(mState);            
+    }
+    
+    private void handleSave() {
+        replaceStateWithWifiLayerInstance();
+
+        String ssid = mSsidEdit.getText().toString();
+        String password = mPasswordEdit.getText().toString();
+        
+        mState.setSsid(ssid);
+        
+        int securityType = getSecurityTypeFromSpinner();
+        
+        if (!TextUtils.isEmpty(password)) {
+            switch (securityType) {
+             
+                case SECURITY_WPA_PERSONAL: {
+                    mState.setSecurity(AccessPointState.WPA);
+                    mState.setPassword(password);
+                    break;
+                }
+                    
+                case SECURITY_WPA2_PERSONAL: {
+                    mState.setSecurity(AccessPointState.WPA2);
+                    mState.setPassword(password);
+                    break;
+                }
+                
+                case SECURITY_AUTO: {
+                    mState.setPassword(password);
+                    break;
+                }
+                    
+                case SECURITY_WEP: {
+                    mState.setSecurity(AccessPointState.WEP);
+                    mState.setPassword(password,
+                            WEP_TYPE_VALUES[mWepTypeSpinner.getSelectedItemPosition()]);
+                    break;
+                }
+                
+            }
+        } else {
+            mState.setSecurity(AccessPointState.OPEN);
+        }
+        
+        if (securityType == SECURITY_NONE) {
+            mState.setSecurity(AccessPointState.OPEN);
+        }
+            
+        if (!mWifiLayer.saveNetwork(mState)) {
+            return;
+        }
+        
+        // Connect right away if they've touched it
+        if (!mWifiLayer.connectToNetwork(mState)) {
+            return;
+        }
+        
+    }
+    
+    /**
+     * Replaces our {@link #mState} with the equal WifiLayer instance.  This is useful after
+     * we unparceled the state previously and before we are calling methods on {@link #mWifiLayer}.
+     * 
+     * @return Whether WifiLayer was able to find an equal state in its set.
+     */
+    private boolean replaceStateWithWifiLayerInstance() {
+        AccessPointState state = mWifiLayer.getWifiLayerApInstance(mState);
+        if (state == null) {
+            return false;
+        }
+        
+        mState = state;
+        return true;
+    }
+    
+    private int getSecurityTypeFromSpinner() {
+        int position = mSecuritySpinner.getSelectedItemPosition();
+        // If there is no AUTO choice, the position needs 1 added to get
+        // to the proper spinner position -> security constants mapping
+        return mAutoSecurityAllowed ? position : position + 1;
+    }
+    
+    private String getEnteredPassword() {
+        return mPasswordEdit != null ? mPasswordEdit.getText().toString() : null;
+    }
+    
+    /**
+     * Call the one you want to hide first.
+     */
+    private void setWepVisible(boolean visible) {
+        setGenericPasswordVisible(visible);
+        int visibility = visible ? View.VISIBLE : View.GONE;
+        mWepTypeSpinner.setVisibility(visibility);
+    }
+    
+    /**
+     * @see #setWepVisible(boolean)
+     */
+    private void setGenericPasswordVisible(boolean visible) {
+        int visibility = visible ? View.VISIBLE : View.GONE;
+        mPasswordText.setVisibility(visibility);
+        mPasswordEdit.setVisibility(visibility);
+        mShowPasswordCheckBox.setVisibility(visibility);
+    }
+    
+    public void onItemSelected(AdapterView parent, View view, int position, long id) {
+        if (parent == mSecuritySpinner) {
+            handleSecurityChange(getSecurityTypeFromSpinner());
+        }
+    }
+
+    public void onNothingSelected(AdapterView parent) {
+    }
+
+    private void handleSecurityChange(int security) {
+        
+        switch (security) {
+            
+            case SECURITY_NONE: {
+                setWepVisible(false);
+                setGenericPasswordVisible(false);
+                break;
+            }
+            
+            case SECURITY_WEP: {
+                setGenericPasswordVisible(false);
+                setWepVisible(true);
+                updatePasswordCaption(AccessPointState.WEP);
+                break;
+            }
+            
+            case SECURITY_AUTO: {
+                setWepVisible(false);
+                setGenericPasswordVisible(mState.hasSecurity());
+                // Shows the generic 'wireless password'
+                updatePasswordCaption(AccessPointState.WPA);
+                break;
+            }
+            
+            case SECURITY_WPA_PERSONAL:
+            case SECURITY_WPA2_PERSONAL: {
+                setWepVisible(false);
+                setGenericPasswordVisible(true);
+                // Both WPA and WPA2 show the same caption, so either is ok
+                updatePasswordCaption(AccessPointState.WPA);
+                break;
+            }
+        }
+    }
+
+    private static int getSignalResId(int signal) {
+        switch (WifiManager.calculateSignalLevel(signal, 4)) {
+            case 0: {
+                return R.string.wifi_signal_0;
+            }
+            case 1: {
+                return R.string.wifi_signal_1;
+            }
+            case 2: {
+                return R.string.wifi_signal_2;
+            }
+            case 3: {
+                return R.string.wifi_signal_3;
+            }
+        }
+        
+        return 0;
+    }
+    
+
+    public void onClick(View v) {
+        if (v == mShowPasswordCheckBox) {
+            setShowPassword(mShowPasswordCheckBox.isChecked());
+        }
+    }
+    
+    private void setShowPassword(boolean showPassword) {
+        if (mPasswordEdit != null) {
+            // Toggle password
+            mPasswordEdit.setTransformationMethod(
+                    showPassword ?
+                            null :
+                            PasswordTransformationMethod.getInstance());
+        }
+    }
+    
+}
diff --git a/src/com/android/settings/wifi/AccessPointPreference.java b/src/com/android/settings/wifi/AccessPointPreference.java
new file mode 100644
index 0000000..10e0947
--- /dev/null
+++ b/src/com/android/settings/wifi/AccessPointPreference.java
@@ -0,0 +1,108 @@
+/*
+ * 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.wifi;
+
+import com.android.settings.R;
+
+import android.net.wifi.WifiManager;
+import android.preference.Preference;
+import android.view.View;
+import android.widget.ImageView;
+
+public class AccessPointPreference extends Preference implements
+        AccessPointState.AccessPointStateCallback {
+    
+    // UI states
+    private static final int[] STATE_ENCRYPTED = { R.attr.state_encrypted };
+    private static final int[] STATE_EMPTY = { };
+    
+    // Signal strength indicator
+    private static final int UI_SIGNAL_LEVELS = 4;
+
+    private WifiSettings mWifiSettings;
+    
+    private AccessPointState mState;
+
+    public AccessPointPreference(WifiSettings wifiSettings, AccessPointState state) {
+        super(wifiSettings, null);
+        
+        mWifiSettings = wifiSettings;
+        mState = state;
+        
+        setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
+        
+        state.setCallback(this);
+        
+        refresh();
+    }
+    
+    public void refresh() {
+        setTitle(mState.getHumanReadableSsid());
+        setSummary(mState.getSummarizedStatus());
+
+        notifyChanged();
+    }
+    
+    public void refreshAccessPointState() {
+        refresh();
+        
+        // The ordering of access points could have changed due to the state change, so
+        // re-evaluate ordering
+        notifyHierarchyChanged();
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        ImageView signal = (ImageView) view.findViewById(R.id.signal);
+        if (mState.seen) {
+            signal.setImageResource(R.drawable.wifi_signal);
+            signal.setImageState(mState.hasSecurity() ? STATE_ENCRYPTED : STATE_EMPTY, true);
+            signal.setImageLevel(getUiSignalLevel());
+        } else {
+            signal.setImageDrawable(null);
+        }
+    }
+
+    private int getUiSignalLevel() {
+        return mState != null ? WifiManager.calculateSignalLevel(mState.signal, UI_SIGNAL_LEVELS)
+                : 0; 
+    }
+
+    /**
+     * Returns the {@link AccessPointState} associated with this preference.
+     * @return The {@link AccessPointState}.
+     */
+    public AccessPointState getAccessPointState() {
+        return mState;
+    }
+    
+    @Override
+    public int compareTo(Preference another) {
+        if (!(another instanceof AccessPointPreference)) {
+            // Let normal preferences go before us.
+            // NOTE: we should only be compared to Preference in our
+            //       category.
+            return 1;
+        }
+        
+        return mState.compareTo(((AccessPointPreference) another).mState);
+    }
+    
+}
+
diff --git a/src/com/android/settings/wifi/AccessPointState.java b/src/com/android/settings/wifi/AccessPointState.java
new file mode 100644
index 0000000..c224954
--- /dev/null
+++ b/src/com/android/settings/wifi/AccessPointState.java
@@ -0,0 +1,879 @@
+/*
+ * 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.wifi;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.net.NetworkInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiConfiguration.AuthAlgorithm;
+import android.net.wifi.WifiConfiguration.GroupCipher;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiConfiguration.PairwiseCipher;
+import android.net.wifi.WifiConfiguration.Protocol;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+public final class AccessPointState implements Comparable<AccessPointState>, Parcelable {
+
+    private static final String TAG = "AccessPointState";
+    
+    // Constants used for different security types
+    public static final String WPA2 = "WPA2";
+    public static final String WPA = "WPA";
+    public static final String WEP = "WEP";
+    public static final String OPEN = "Open";
+
+    /** String present in capabilities if the scan result is ad-hoc */
+    private static final String ADHOC_CAPABILITY = "[IBSS]";
+    /** String present in capabilities if the scan result is enterprise secured */
+    private static final String ENTERPRISE_CAPABILITY = "-EAP-";
+    
+    // Localized strings for different security types
+    private static String LOCALIZED_WPA2;
+    private static String LOCALIZED_WPA;
+    private static String LOCALIZED_WEP;
+    private static String LOCALIZED_OPEN;
+    private static String LOCALIZED_UNKNOWN;
+    private static String LOCALIZED_VERBOSE_WPA2;
+    private static String LOCALIZED_VERBOSE_WPA;
+    private static String LOCALIZED_VERBOSE_WEP;
+    private static String LOCALIZED_VERBOSE_OPEN;
+
+    
+    // Localized strings for various messages
+    private static String SUMMARY_NOT_IN_RANGE;
+    private static String SUMMARY_REMEMBERED;
+    private static String SUMMARY_CONNECTION_FAILED;
+    
+    public static final String BSSID_ANY = "any";
+    public static final int NETWORK_ID_NOT_SET = -1;
+    /** This should be used with care! */
+    static final int NETWORK_ID_ANY = -2;
+    
+    public static final int MATCH_NONE = 0;
+    public static final int MATCH_WEAK = 1;
+    public static final int MATCH_STRONG = 2;
+    public static final int MATCH_EXACT = 3;
+    
+    // Don't set these directly, use the setters.
+    public int networkId;
+    public int priority;
+    public boolean hiddenSsid;
+    public int linkSpeed;
+    public int ipAddress;
+    public String bssid;
+    public String ssid;
+    public int signal;
+    public boolean primary;
+    public boolean seen;
+    public boolean configured;
+    public NetworkInfo.DetailedState status;
+    public String security;
+    public boolean disabled;
+    
+    /**
+     * Use this for sorting based on signal strength. It is a heavily-damped
+     * time-averaged weighted signal.
+     */
+    private float signalForSorting = Float.MIN_VALUE;
+    
+    private static final float DAMPING_FACTOR = 0.2f;
+    
+    /**
+     * This will be a user entered password, and NOT taken from wpa_supplicant
+     * (since it would give us *)
+     */
+    private String mPassword; 
+    private boolean mConfigHadPassword;
+    
+    public static final int WEP_PASSWORD_AUTO = 0;
+    public static final int WEP_PASSWORD_ASCII = 1;
+    public static final int WEP_PASSWORD_HEX = 2;
+    private int mWepPasswordType;
+    
+    private Context mContext;
+
+    /**
+     * If > 0, don't refresh (changes are being batched), use
+     * {@link #blockRefresh()} and {@link #unblockRefresh()} only.
+     */
+    private int mBlockRefresh;
+    /**
+     * This will be set by {@link #requestRefresh} and shouldn't be written to
+     * elsewhere.
+     */
+    private boolean mNeedsRefresh;    
+    
+    private AccessPointStateCallback mCallback;
+    
+    private StringBuilder mSummaryBuilder = new StringBuilder();
+    
+    interface AccessPointStateCallback {
+        void refreshAccessPointState();
+    }
+    
+    public AccessPointState(Context context) {
+        this();
+        
+        setContext(context);
+    }
+
+    private AccessPointState() {
+        bssid = BSSID_ANY;
+        ssid = "";
+        networkId = NETWORK_ID_NOT_SET;
+        hiddenSsid = false;
+    }
+
+    void setContext(Context context) {
+        mContext = context;
+        setStrings();
+    }
+
+    private void setStrings() {
+        final Context c = mContext;
+
+        if (SUMMARY_NOT_IN_RANGE == null && c != null) {
+            SUMMARY_NOT_IN_RANGE = c.getString(R.string.summary_not_in_range);
+            SUMMARY_REMEMBERED = c.getString(R.string.summary_remembered);
+            SUMMARY_CONNECTION_FAILED = c.getString(R.string.summary_connection_failed);
+            
+            LOCALIZED_OPEN = c.getString(R.string.wifi_security_open);
+            LOCALIZED_WEP = c.getString(R.string.wifi_security_wep);
+            LOCALIZED_WPA = c.getString(R.string.wifi_security_wpa);
+            LOCALIZED_WPA2 = c.getString(R.string.wifi_security_wpa2);
+
+            LOCALIZED_VERBOSE_OPEN = c.getString(R.string.wifi_security_verbose_open);
+            LOCALIZED_VERBOSE_WEP = c.getString(R.string.wifi_security_verbose_wep);
+            LOCALIZED_VERBOSE_WPA = c.getString(R.string.wifi_security_verbose_wpa);
+            LOCALIZED_VERBOSE_WPA2 = c.getString(R.string.wifi_security_verbose_wpa2);
+
+            LOCALIZED_UNKNOWN = c.getString(R.string.wifi_security_unknown);
+        }
+    }
+    
+    public void setNetworkId(int networkId) {
+        this.networkId = networkId;
+    }
+    
+    public void setBssid(String bssid) {
+        if (bssid != null) {
+            // If the BSSID is a wildcard, do NOT let a specific BSSID replace it
+            if (!this.bssid.equals(BSSID_ANY)) {
+                this.bssid = bssid;
+            }
+        }
+    }
+
+    private String getWpaSupplicantBssid() {
+        return bssid.equals(BSSID_ANY) ? null : bssid;
+    }
+    
+    public static String convertToQuotedString(String string) {
+        if (TextUtils.isEmpty(string)) {
+            return "";
+        }
+        
+        final int lastPos = string.length() - 1;
+        if (lastPos < 0 || (string.charAt(0) == '"' && string.charAt(lastPos) == '"')) {
+            return string;
+        }
+        
+        return "\"" + string + "\"";
+    }
+    
+    public void setPrimary(boolean primary) {
+        if (this.primary != primary) {
+            this.primary = primary;
+            requestRefresh();
+        }
+    }
+    
+    public void setSeen(boolean seen) {
+        if (this.seen != seen) {
+            this.seen = seen;
+            requestRefresh();
+        }
+    }
+    
+    public void setDisabled(boolean disabled) {
+        if (this.disabled != disabled) {
+            this.disabled = disabled;
+            requestRefresh();
+        }
+    }
+    
+    public void setSignal(int signal) {
+
+        if (signalForSorting == Float.MIN_VALUE) {
+            signalForSorting = signal;
+        } else {
+            signalForSorting = (DAMPING_FACTOR * signal) + ((1-DAMPING_FACTOR) * signalForSorting);
+        }
+
+        if (this.signal != signal) {
+            this.signal = signal;
+            requestRefresh();
+        }
+    }
+    
+    public String getHumanReadableSsid() {
+        if (TextUtils.isEmpty(ssid)) {
+            return "";
+        }
+        
+        final int lastPos = ssid.length() - 1;
+        if (ssid.charAt(0) == '"' && ssid.charAt(lastPos) == '"') {
+            return ssid.substring(1, lastPos);
+        }
+        
+        return ssid;
+    }
+    
+    public void setSsid(String ssid) {
+        if (ssid != null) {
+            this.ssid = convertToQuotedString(ssid);
+            requestRefresh();
+        }
+    }
+    
+    public void setPriority(int priority) {
+        if (this.priority != priority) {
+            this.priority = priority;
+            requestRefresh();
+        }
+    }
+
+    public void setHiddenSsid(boolean hiddenSsid) {
+        if (this.hiddenSsid != hiddenSsid) {
+            this.hiddenSsid = hiddenSsid;
+            requestRefresh();
+        }
+    }
+
+    public void setLinkSpeed(int linkSpeed) {
+        if (this.linkSpeed != linkSpeed) {
+            this.linkSpeed = linkSpeed;
+            requestRefresh();
+        }
+    }
+    
+    public void setIpAddress(int address) {
+        if (ipAddress != address) {
+            ipAddress = address;
+            requestRefresh();
+        }
+    }
+    
+    public void setConfigured(boolean configured) {
+        if (this.configured != configured) {
+            this.configured = configured;
+            requestRefresh();
+        }
+    }
+    
+    public void setStatus(NetworkInfo.DetailedState status) {
+        if (this.status != status) {
+            this.status = status;
+            requestRefresh();
+        }
+    }
+    
+    public void setSecurity(String security) {
+        if (TextUtils.isEmpty(this.security) || !this.security.equals(security)) {
+            this.security = security;
+            requestRefresh();
+        }
+    }
+
+    public boolean hasSecurity() {
+        return security != null && !security.contains(OPEN);
+    }
+    
+    public String getHumanReadableSecurity() {
+        if (security.equals(OPEN)) return LOCALIZED_OPEN;
+        else if (security.equals(WEP)) return LOCALIZED_WEP;
+        else if (security.equals(WPA)) return LOCALIZED_WPA;
+        else if (security.equals(WPA2)) return LOCALIZED_WPA2;
+        
+        return LOCALIZED_UNKNOWN;
+    }
+    
+    public void updateFromScanResult(ScanResult scanResult) {
+        blockRefresh();
+        
+        // We don't keep specific AP BSSIDs and instead leave that as wildcard
+        
+        setSeen(true);
+        setSsid(scanResult.SSID);
+        if (networkId == NETWORK_ID_NOT_SET) {
+            // Since ScanResults don't cross-reference network ID, we set it as a wildcard
+            setNetworkId(NETWORK_ID_ANY);
+        }
+        setSignal(scanResult.level);
+        setSecurity(getScanResultSecurity(scanResult));
+        unblockRefresh();
+    }
+    
+    /**
+     * @return The security of a given {@link ScanResult}.
+     */
+    public static String getScanResultSecurity(ScanResult scanResult) {
+        final String cap = scanResult.capabilities;
+        final String[] securityModes = { WEP, WPA, WPA2 }; 
+        for (int i = securityModes.length - 1; i >= 0; i--) {
+            if (cap.contains(securityModes[i])) {
+                return securityModes[i];
+            }
+        }
+        
+        return OPEN;
+    }
+    
+    /**
+     * @return Whether the given ScanResult represents an adhoc network.
+     */
+    public static boolean isAdhoc(ScanResult scanResult) {
+        return scanResult.capabilities.contains(ADHOC_CAPABILITY);
+    }
+    
+    /**
+     * @return Whether the given ScanResult has enterprise security.
+     */
+    public static boolean isEnterprise(ScanResult scanResult) {
+        return scanResult.capabilities.contains(ENTERPRISE_CAPABILITY);
+    }
+    
+    public void updateFromWifiConfiguration(WifiConfiguration wifiConfig) {
+        if (wifiConfig != null) {
+            blockRefresh();
+            setBssid(wifiConfig.BSSID);
+            setNetworkId(wifiConfig.networkId);
+            setPriority(wifiConfig.priority);
+            setHiddenSsid(wifiConfig.hiddenSSID);
+            setSsid(wifiConfig.SSID);
+            setConfigured(true);
+            setDisabled(wifiConfig.status == WifiConfiguration.Status.DISABLED);
+            parseWifiConfigurationSecurity(wifiConfig);
+            unblockRefresh();
+        }
+    }
+    
+    public void setPassword(String password) {
+        setPassword(password, WEP_PASSWORD_AUTO);
+    }
+    
+    public void setPassword(String password, int wepPasswordType) {
+        mPassword = password;
+        mWepPasswordType = wepPasswordType;
+    }
+    
+    public boolean hasPassword() {
+        return !TextUtils.isEmpty(mPassword) || mConfigHadPassword; 
+    }
+    
+    private static boolean hasPassword(WifiConfiguration wifiConfig) {
+        return !TextUtils.isEmpty(wifiConfig.preSharedKey)
+                || !TextUtils.isEmpty(wifiConfig.wepKeys[0])
+                || !TextUtils.isEmpty(wifiConfig.wepKeys[1])
+                || !TextUtils.isEmpty(wifiConfig.wepKeys[2])
+                || !TextUtils.isEmpty(wifiConfig.wepKeys[3]);        
+    }
+    
+    private void parseWifiConfigurationSecurity(WifiConfiguration wifiConfig) {
+        setSecurity(getWifiConfigurationSecurity(wifiConfig));
+        mConfigHadPassword = hasPassword(wifiConfig);
+    }
+    
+    /**
+     * @return The security of a given {@link WifiConfiguration}.
+     */
+    public static String getWifiConfigurationSecurity(WifiConfiguration wifiConfig) {
+
+        if (wifiConfig.allowedKeyManagement.get(KeyMgmt.NONE)) {
+            // If we never set group ciphers, wpa_supplicant puts all of them.
+            // For open, we don't set group ciphers.
+            // For WEP, we specifically only set WEP40 and WEP104, so CCMP
+            // and TKIP should not be there.
+            if (!wifiConfig.allowedGroupCiphers.get(GroupCipher.CCMP)
+                    && (wifiConfig.allowedGroupCiphers.get(GroupCipher.WEP40)
+                            || wifiConfig.allowedGroupCiphers.get(GroupCipher.WEP104))) {
+                return WEP;
+            } else {
+                return OPEN;
+            }
+        } else if (wifiConfig.allowedProtocols.get(Protocol.RSN)) {
+            return WPA2;
+        } else if (wifiConfig.allowedProtocols.get(Protocol.WPA)) {
+            return WPA;
+        } else {
+            Log.w(TAG, "Unknown security type from WifiConfiguration, falling back on open.");
+            return OPEN;
+        }
+    }
+    
+    public void updateFromWifiInfo(WifiInfo wifiInfo, NetworkInfo.DetailedState state) {
+        if (wifiInfo != null) {
+            blockRefresh();
+            setBssid(wifiInfo.getBSSID());
+            setLinkSpeed(wifiInfo.getLinkSpeed());
+            setNetworkId(wifiInfo.getNetworkId());
+            setIpAddress(wifiInfo.getIpAddress());
+            setSsid(wifiInfo.getSSID());
+            if (state != null) {
+                setStatus(state);
+            }
+            setHiddenSsid(wifiInfo.getHiddenSSID());
+            unblockRefresh();
+        }
+    }
+    
+    /**
+     * @return Whether this AP can be connected to at the moment.
+     */
+    public boolean isConnectable() {
+        return !primary && seen;
+    }
+
+    /**
+     * @return Whether this AP can be forgotten at the moment.
+     */
+    public boolean isForgetable() {
+        return configured;
+    }
+    
+    /**
+     * Updates the state as if it were never configured.
+     * <p>
+     * Note: This will not pass the forget call to the Wi-Fi API.
+     */
+    public void forget() {
+        blockRefresh();
+        setConfigured(false);
+        setNetworkId(NETWORK_ID_NOT_SET);
+        setPrimary(false);
+        setStatus(null);
+        setDisabled(false);
+        unblockRefresh();
+    }
+    
+    public void updateWifiConfiguration(WifiConfiguration config) {
+        config.BSSID = getWpaSupplicantBssid();
+        config.priority = priority;
+        config.hiddenSSID = hiddenSsid;
+        config.SSID = convertToQuotedString(ssid);
+        
+        setupSecurity(config);
+    }
+    
+    private void setupSecurity(WifiConfiguration config) {
+        config.allowedAuthAlgorithms.clear();
+        config.allowedGroupCiphers.clear();
+        config.allowedKeyManagement.clear();
+        config.allowedPairwiseCiphers.clear();
+        config.allowedProtocols.clear();
+        
+        if (TextUtils.isEmpty(security)) {
+            security = OPEN;
+            Log.w(TAG, "Empty security, assuming open");
+        }
+        
+        if (security.equals(WEP)) {
+            
+            // If password is empty, it should be left untouched
+            if (!TextUtils.isEmpty(mPassword)) {
+                if (mWepPasswordType == WEP_PASSWORD_AUTO) {
+                    if (isHexWepKey(mPassword)) {
+                        config.wepKeys[0] = mPassword;
+                    } else {
+                        config.wepKeys[0] = convertToQuotedString(mPassword);
+                    }
+                } else {
+                    config.wepKeys[0] = mWepPasswordType == WEP_PASSWORD_ASCII
+                            ? convertToQuotedString(mPassword)
+                            : mPassword;
+                }
+            }
+            
+            config.wepTxKeyIndex = 0;
+            
+            config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
+            config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
+
+            config.allowedKeyManagement.set(KeyMgmt.NONE);
+            
+            config.allowedGroupCiphers.set(GroupCipher.WEP40);
+            config.allowedGroupCiphers.set(GroupCipher.WEP104);
+            
+        } else if (security.equals(WPA) || security.equals(WPA2)){
+            config.allowedGroupCiphers.set(GroupCipher.TKIP);
+            config.allowedGroupCiphers.set(GroupCipher.CCMP);
+            
+            config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+            
+            config.allowedPairwiseCiphers.set(PairwiseCipher.CCMP);
+            config.allowedPairwiseCiphers.set(PairwiseCipher.TKIP);
+
+            config.allowedProtocols.set(security.equals(WPA2) ? Protocol.RSN : Protocol.WPA);
+            
+            // If password is empty, it should be left untouched
+            if (!TextUtils.isEmpty(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)) {
+            config.allowedKeyManagement.set(KeyMgmt.NONE);
+        }
+    }
+    
+    private static boolean isHexWepKey(String wepKey) {
+        final int len = wepKey.length();
+        
+        // WEP-40, WEP-104, and some vendors using 256-bit WEP (WEP-232?)
+        if (len != 10 && len != 26 && len != 58) {
+            return false;
+        }
+        
+        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;
+            }
+        }
+        
+        return true;
+    }
+    
+    public void setCallback(AccessPointStateCallback callback) {
+        mCallback = callback;
+    }
+    
+    void blockRefresh() {
+        mBlockRefresh++;
+    }
+    
+    void unblockRefresh() {
+        if (--mBlockRefresh == 0 && mNeedsRefresh) {
+            requestRefresh();
+        }
+    }
+    
+    private void requestRefresh() {
+        if (mBlockRefresh > 0) {
+            mNeedsRefresh = true;
+            return;
+        }
+        
+        if (mCallback != null) {
+            mCallback.refreshAccessPointState();
+        }
+        
+        mNeedsRefresh = false;
+    }
+    
+    /**
+     * {@inheritDoc}
+     * @see #hashCode()
+     * @see #equals(Object)
+     */
+    public int matches(int otherNetworkId, String otherBssid, String otherSsid,
+            String otherSecurity) {
+        
+        // Whenever this method is touched, please ensure #equals and #hashCode
+        // still work with the changes here!
+        
+        if (otherSsid == null) {
+            if (WifiLayer.LOGV) {
+                Log.w(TAG, "BSSID: " + otherBssid + ", SSID: " + otherSsid);
+            }
+            return MATCH_NONE;
+        }
+
+        /*
+         * If we both have 'security' set, it must match (an open network still
+         * has 'security' set to OPEN)
+         */
+        if (security != null && otherSecurity != null) {
+            if (!security.equals(otherSecurity)) {
+                return MATCH_NONE;
+            }
+        }
+        
+        // WifiConfiguration gives an empty bssid as a BSSID wildcard
+        if (TextUtils.isEmpty(otherBssid)) {
+            otherBssid = AccessPointState.BSSID_ANY;
+        }
+
+        final boolean networkIdMatches = networkId == otherNetworkId;
+        if (!networkIdMatches && networkId != NETWORK_ID_ANY && otherNetworkId != NETWORK_ID_ANY) {
+            // Network IDs don't match (e.g., 1 & 2 or unset & 1) and neither is a wildcard
+            return MATCH_NONE;
+        }
+        
+        if (networkIdMatches && otherNetworkId != NETWORK_ID_NOT_SET
+                && otherNetworkId != NETWORK_ID_ANY) {
+            // Network ID matches (they're set to the same ID)
+            return MATCH_EXACT;
+        }
+        
+        // So now, network IDs aren't set or at least one is a wildcard 
+        
+        final boolean bssidMatches = bssid.equals(otherBssid);
+        final boolean otherBssidIsWildcard = otherBssid.equals(BSSID_ANY);
+        if (bssidMatches && !otherBssidIsWildcard) {
+            // BSSID matches (and neither is a wildcard)
+            return MATCH_STRONG;
+        }
+
+        if (!bssidMatches && !bssid.equals(BSSID_ANY) && !otherBssidIsWildcard) {
+            // BSSIDs don't match (e.g., 00:24:21:21:42:12 & 42:12:44:21:22:52)
+            // and neither is a wildcard
+            return MATCH_NONE;
+        }
+        
+        // So now, BSSIDs are both wildcards
+        
+        final boolean ssidMatches = ssid.equals(otherSsid); 
+        if (ssidMatches) {
+            // SSID matches
+            return MATCH_WEAK;
+        }
+
+        return MATCH_NONE;
+    }
+    
+    /**
+     * {@inheritDoc}
+     * @see #matches(int, String, String)
+     * @see #equals(Object)
+     */
+    @Override
+    public int hashCode() {
+        // Two equal() objects must have same hashCode.
+        // With Wi-Fi, the broadest match is if two SSIDs are the same.  The finer-grained matches
+        // imply this (for example, the same network IDs means the same WifiConfiguration which
+        // means the same SSID).
+        // See #matches for the exact matching algorithm we use.
+        return ssid != null ? ssid.hashCode() : 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see #matches(int, String, String)
+     * @see #hashCode()
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!o.getClass().equals(getClass())) {
+            return false;
+        }
+        
+        final AccessPointState other = (AccessPointState) o;
+
+        // To see which conditions cause two AccessPointStates to be equal, see
+        // where #matches returns MATCH_WEAK or greater.
+        
+        return matches(other.networkId, other.bssid, other.ssid, other.security) >= MATCH_WEAK;
+    }
+
+    public int matchesWifiConfiguration(WifiConfiguration wifiConfig) {
+        String security = getWifiConfigurationSecurity(wifiConfig);
+        return matches(wifiConfig.networkId, wifiConfig.BSSID, wifiConfig.SSID, security);
+    }
+    
+    String getSummarizedStatus() {
+        StringBuilder sb = mSummaryBuilder;
+        sb.delete(0, sb.length());
+        
+        if (primary && status != null) {
+            buildSummary(sb, WifiStatus.getPrintable(mContext, status), true);
+            
+        } else if (!seen) {
+            buildSummary(sb, SUMMARY_NOT_IN_RANGE, true);
+
+            // Remembered comes second in this case
+            if (!primary && configured) {
+                buildSummary(sb, SUMMARY_REMEMBERED, true);
+            }
+            
+        } else {
+            if (configured && disabled) {
+                // The connection failure overrides all in this case
+                return SUMMARY_CONNECTION_FAILED;
+            }
+
+            // Remembered comes first in this case
+            if (!primary && configured) {
+                buildSummary(sb, SUMMARY_REMEMBERED, true);
+            }
+            
+            // If it is seen (and not the primary), show the security type
+            String verboseSecurity = getVerboseSecurity();
+            if (verboseSecurity != null) {
+                buildSummary(sb, verboseSecurity, true);
+            }
+        }
+        
+        return sb.toString();
+    }
+    
+    private String getVerboseSecurity() {
+        if (WEP.equals(security)) {
+            return LOCALIZED_VERBOSE_WEP;
+        } else if (WPA.equals(security)) {
+            return LOCALIZED_VERBOSE_WPA;
+        } else if (WPA2.equals(security)) {
+            return LOCALIZED_VERBOSE_WPA2;
+        } else if (OPEN.equals(security)) {
+            return LOCALIZED_VERBOSE_OPEN;
+        } else {
+            return null;
+        }
+    }
+    
+    private void buildSummary(StringBuilder sb, String string, boolean autoLowerCaseFirstLetter) {
+        if (sb.length() == 0) {
+            sb.append(string);
+        } else {
+            sb.append(", ");
+            if (autoLowerCaseFirstLetter) {
+                // Convert first letter to lowercase
+                sb.append(Character.toLowerCase(string.charAt(0))).append(string, 1,
+                        string.length());
+            } else {
+                sb.append(string);
+            }
+        }
+    }
+    
+    public int compareTo(AccessPointState other) {
+        // This ranks the states for displaying in the AP list, not for
+        // connecting to (wpa_supplicant does that using the WifiConfiguration's
+        // priority field).
+        
+        // Clarity > efficiency, of this logic:
+        int comparison;
+        
+        // Primary
+        comparison = (other.primary ? 1 : 0) - (primary ? 1 : 0);
+        if (comparison != 0) return comparison;
+        
+        // Currently seen (similar to, but not always the same as within range)
+        comparison = (other.seen ? 1 : 0) - (seen ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        // Configured
+        comparison = (other.configured ? 1 : 0) - (configured ? 1 : 0);
+        if (comparison != 0) return comparison;
+
+        if (!configured) {
+            // Neither are configured
+
+            // Open network
+            comparison = (hasSecurity() ? 1 : 0) - (other.hasSecurity() ? 1 : 0);
+            if (comparison != 0) return comparison;
+        }
+
+        // Signal strength
+        comparison = (int) (other.signalForSorting - signalForSorting);
+        if (comparison != 0) return comparison;
+
+        // Alphabetical
+        return ssid.compareToIgnoreCase(other.ssid);
+    }
+
+    public String toString() {
+        return ssid + " (" + bssid + ", " + networkId + ", " + super.toString() + ")";
+    }
+    
+    /** Implement the Parcelable interface */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(bssid);
+        dest.writeInt(configured ? 1 : 0);
+        dest.writeInt(ipAddress);
+        dest.writeInt(linkSpeed);
+        dest.writeInt(networkId);
+        dest.writeInt(primary ? 1 : 0);
+        dest.writeInt(priority);
+        dest.writeInt(hiddenSsid ? 1 : 0);
+        dest.writeString(security);
+        dest.writeInt(seen ? 1 : 0);
+        dest.writeInt(disabled ? 1 : 0);
+        dest.writeInt(signal);
+        dest.writeString(ssid);
+        dest.writeString(status != null ? status.toString() : null);
+        dest.writeString(mPassword);
+        dest.writeInt(mConfigHadPassword ? 1 : 0);
+        dest.writeInt(mWepPasswordType);
+    }
+
+    /** Implement the Parcelable interface */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface */
+    public static final Creator<AccessPointState> CREATOR =
+        new Creator<AccessPointState>() {
+            public AccessPointState createFromParcel(Parcel in) {
+                AccessPointState state = new AccessPointState();
+                state.bssid = in.readString();
+                state.configured = in.readInt() == 1;
+                state.ipAddress = in.readInt();
+                state.linkSpeed = in.readInt();
+                state.networkId = in.readInt();
+                state.primary = in.readInt() == 1;
+                state.priority = in.readInt();
+                state.hiddenSsid = in.readInt() == 1;
+                state.security = in.readString();
+                state.seen = in.readInt() == 1;
+                state.disabled = in.readInt() == 1;
+                state.signal = in.readInt();
+                state.ssid = in.readString();
+                String statusStr = in.readString();
+                if (statusStr != null) {
+                    state.status = NetworkInfo.DetailedState.valueOf(statusStr);
+                }
+                state.mPassword = in.readString();
+                state.mConfigHadPassword = in.readInt() == 1;
+                state.mWepPasswordType = in.readInt();
+                return state;
+            }
+
+            public AccessPointState[] newArray(int size) {
+                return new AccessPointState[size];
+            }
+        };
+
+        
+}
diff --git a/src/com/android/settings/wifi/AdvancedSettings.java b/src/com/android/settings/wifi/AdvancedSettings.java
new file mode 100644
index 0000000..323d5c7
--- /dev/null
+++ b/src/com/android/settings/wifi/AdvancedSettings.java
@@ -0,0 +1,274 @@
+/*
+ * 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.wifi;
+
+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;
+import android.provider.Settings.System;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+public class AdvancedSettings 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 static final String KEY_SLEEP_POLICY = "sleep_policy";
+    
+    private String[] mSettingNames = {
+            System.WIFI_STATIC_IP, System.WIFI_STATIC_GATEWAY, System.WIFI_STATIC_NETMASK,
+            System.WIFI_STATIC_DNS1, System.WIFI_STATIC_DNS2
+    };
+    
+    private String[] mPreferenceKeys = {
+            "ip_address", "gateway", "netmask", "dns1", "dns2"
+    };
+    
+    private CheckBoxPreference mUseStaticIpCheckBox;
+    
+    private static final int MENU_ITEM_SAVE = Menu.FIRST;
+    private static final int MENU_ITEM_CANCEL = Menu.FIRST + 1;
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        addPreferencesFromResource(R.xml.wifi_advanced_settings);
+        
+        mUseStaticIpCheckBox = (CheckBoxPreference) findPreference(KEY_USE_STATIC_IP);
+
+        for (int i = 0; i < mPreferenceKeys.length; i++) {
+            Preference preference = findPreference(mPreferenceKeys[i]);
+            preference.setOnPreferenceChangeListener(this);
+        }
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        
+        updateUi();
+        initNumChannelsPreference();
+        initSleepPolicyPreference();
+        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();
+            pref.setEnabled(false);
+            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);
+        pref.setEnabled(true);
+        int numChannels = wifiManager.getNumAllowedChannels();
+        if (numChannels >= 0) {
+            pref.setValue(String.valueOf(numChannels));
+        }
+    }
+    
+    private void initSleepPolicyPreference() {
+        ListPreference pref = (ListPreference) findPreference(KEY_SLEEP_POLICY);
+        pref.setOnPreferenceChangeListener(this);
+        int value = Settings.System.getInt(getContentResolver(),
+                Settings.System.WIFI_SLEEP_POLICY,Settings. System.WIFI_SLEEP_POLICY_DEFAULT);
+        pref.setValue(String.valueOf(value));
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+    
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            updateSettingsProvider();
+        }
+    
+        return super.onKeyDown(keyCode, event);
+    }
+
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        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 if (key.equals(KEY_SLEEP_POLICY)) {
+            try {
+                Settings.System.putInt(getContentResolver(),
+                        Settings.System.WIFI_SLEEP_POLICY, Integer.parseInt(((String) newValue)));
+            } catch (NumberFormatException e) {
+                Toast.makeText(this, R.string.wifi_setting_sleep_policy_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);
+        }
+        
+        return true;
+    }
+
+    private boolean isIpAddress(String value) {
+        
+        int start = 0;
+        int end = value.indexOf('.');
+        int numBlocks = 0;
+        
+        while (start < value.length()) {
+            
+            if (end == -1) {
+                end = value.length();
+            }
+
+            try {
+                int block = Integer.parseInt(value.substring(start, end));
+                if ((block > 255) || (block < 0)) {
+                    return false;
+                }
+            } catch (NumberFormatException e) {
+                return false;
+            }
+            
+            numBlocks++;
+            
+            start = end + 1;
+            end = value.indexOf('.', start);
+        }
+        
+        return numBlocks == 4;
+    }
+    
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        
+        menu.add(0, MENU_ITEM_SAVE, 0, R.string.wifi_ip_settings_menu_save)
+                .setIcon(android.R.drawable.ic_menu_save);
+
+        menu.add(0, MENU_ITEM_CANCEL, 0, R.string.wifi_ip_settings_menu_cancel)
+                .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
+
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+
+        switch (item.getItemId()) {
+        
+            case MENU_ITEM_SAVE:
+                updateSettingsProvider();
+                finish();
+                return true;
+                
+            case MENU_ITEM_CANCEL:
+                finish();
+                return true;
+        }
+        
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void updateUi() {
+        ContentResolver contentResolver = getContentResolver();
+        
+        mUseStaticIpCheckBox.setChecked(System.getInt(contentResolver,
+                System.WIFI_USE_STATIC_IP, 0) != 0);
+        
+        for (int i = 0; i < mSettingNames.length; i++) {
+            EditTextPreference preference = (EditTextPreference) findPreference(mPreferenceKeys[i]);
+            String settingValue = System.getString(contentResolver, mSettingNames[i]);
+            preference.setText(settingValue);
+            preference.setSummary(settingValue);
+        }
+    }
+    
+    private void updateSettingsProvider() {
+        ContentResolver contentResolver = getContentResolver();
+
+        System.putInt(contentResolver, System.WIFI_USE_STATIC_IP,
+                mUseStaticIpCheckBox.isChecked() ? 1 : 0);
+        
+        for (int i = 0; i < mSettingNames.length; i++) {
+            EditTextPreference preference = (EditTextPreference) findPreference(mPreferenceKeys[i]);
+            System.putString(contentResolver, mSettingNames[i], preference.getText());
+        }
+    }
+    
+    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/WifiEnabler.java b/src/com/android/settings/wifi/WifiEnabler.java
new file mode 100644
index 0000000..88cfe06
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiEnabler.java
@@ -0,0 +1,188 @@
+/*
+ * 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.wifi;
+
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
+
+import com.android.settings.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
+import android.preference.Preference;
+import android.preference.CheckBoxPreference;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+public class WifiEnabler implements Preference.OnPreferenceChangeListener {
+    
+    private static final boolean LOCAL_LOGD = Config.LOGD || WifiLayer.LOGV;
+    private static final String TAG = "SettingsWifiEnabler";
+    
+    private final Context mContext; 
+    private final WifiManager mWifiManager;
+    private final CheckBoxPreference mWifiCheckBoxPref;
+    private final CharSequence mOriginalSummary;
+    
+    private final IntentFilter mWifiStateFilter;
+    private final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                handleWifiStateChanged(
+                        intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WIFI_STATE_UNKNOWN),
+                        intent.getIntExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE,
+                                WIFI_STATE_UNKNOWN));
+            } else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                handleNetworkStateChanged(
+                        (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
+            }
+        }
+    };
+    
+    public WifiEnabler(Context context, WifiManager wifiManager,
+            CheckBoxPreference wifiCheckBoxPreference) {
+        mContext = context;
+        mWifiCheckBoxPref = wifiCheckBoxPreference;
+        mWifiManager = wifiManager;
+        
+        mOriginalSummary = wifiCheckBoxPreference.getSummary();
+        wifiCheckBoxPreference.setPersistent(false);
+        
+        mWifiStateFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        mWifiStateFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+    }
+
+    public void resume() {
+        int state = mWifiManager.getWifiState();
+        // This is the widget enabled state, not the preference toggled state
+        mWifiCheckBoxPref.setEnabled(state == WIFI_STATE_ENABLED || state == WIFI_STATE_DISABLED
+                || state == WIFI_STATE_UNKNOWN);
+
+        mContext.registerReceiver(mWifiStateReceiver, mWifiStateFilter);
+        mWifiCheckBoxPref.setOnPreferenceChangeListener(this);
+    }
+    
+    public void pause() {
+        mContext.unregisterReceiver(mWifiStateReceiver);
+        mWifiCheckBoxPref.setOnPreferenceChangeListener(null);
+    }
+    
+    public boolean onPreferenceChange(Preference preference, Object value) {
+        // Turn on/off Wi-Fi
+        setWifiEnabled((Boolean) value);
+        
+        // Don't update UI to opposite state until we're sure
+        return false;
+    }
+    
+    private void setWifiEnabled(final boolean enable) {
+        // Disable button
+        mWifiCheckBoxPref.setEnabled(false);
+        
+        if (!mWifiManager.setWifiEnabled(enable)) {
+            mWifiCheckBoxPref.setSummary(enable ? R.string.error_starting : R.string.error_stopping);
+        }
+    }
+    
+    private void handleWifiStateChanged(int wifiState, int previousWifiState) {
+
+        if (LOCAL_LOGD) {
+            Log.d(TAG, "Received wifi state changed from "
+                    + getHumanReadableWifiState(previousWifiState) + " to "
+                    + getHumanReadableWifiState(wifiState));
+        }
+        
+        if (wifiState == WIFI_STATE_DISABLED || wifiState == WIFI_STATE_ENABLED) {
+            mWifiCheckBoxPref.setChecked(wifiState == WIFI_STATE_ENABLED);
+            mWifiCheckBoxPref
+                    .setSummary(wifiState == WIFI_STATE_DISABLED ? mOriginalSummary : null);
+            
+            mWifiCheckBoxPref.setEnabled(isEnabledByDependency());
+            
+        } else if (wifiState == WIFI_STATE_DISABLING || wifiState == WIFI_STATE_ENABLING) {
+            mWifiCheckBoxPref.setSummary(wifiState == WIFI_STATE_ENABLING ? R.string.wifi_starting
+                    : R.string.wifi_stopping);
+            
+        } else if (wifiState == WIFI_STATE_UNKNOWN) {
+            int message = R.string.wifi_error;
+            if (previousWifiState == WIFI_STATE_ENABLING) message = R.string.error_starting;
+            else if (previousWifiState == WIFI_STATE_DISABLING) message = R.string.error_stopping;
+            
+            mWifiCheckBoxPref.setChecked(false);
+            mWifiCheckBoxPref.setSummary(message);
+            mWifiCheckBoxPref.setEnabled(true);
+        }
+    }
+
+    private void handleNetworkStateChanged(NetworkInfo networkInfo) {
+
+        if (LOCAL_LOGD) {
+            Log.d(TAG, "Received network state changed to " + networkInfo);
+        }
+        
+        if (mWifiManager.isWifiEnabled()) {
+            String summary = WifiStatus.getStatus(mContext, 
+                    mWifiManager.getConnectionInfo().getSSID(), networkInfo.getDetailedState());
+            mWifiCheckBoxPref.setSummary(summary);
+        }
+    }
+
+    private boolean isEnabledByDependency() {
+        Preference dep = getDependencyPreference();
+        if (dep == null) {
+            return true;
+        }
+        
+        return !dep.shouldDisableDependents();
+    }
+    
+    private Preference getDependencyPreference() {
+        String depKey = mWifiCheckBoxPref.getDependency();
+        if (TextUtils.isEmpty(depKey)) {
+            return null;
+        }
+        
+        return mWifiCheckBoxPref.getPreferenceManager().findPreference(depKey);
+    }
+    
+    private static String getHumanReadableWifiState(int wifiState) {
+        switch (wifiState) {
+            case WIFI_STATE_DISABLED:
+                return "Disabled";
+            case WIFI_STATE_DISABLING:
+                return "Disabling";
+            case WIFI_STATE_ENABLED:
+                return "Enabled";
+            case WIFI_STATE_ENABLING:
+                return "Enabling";
+            case WIFI_STATE_UNKNOWN:
+                return "Unknown";
+            default:
+                return "Some other state!";    
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/WifiLayer.java b/src/com/android/settings/wifi/WifiLayer.java
new file mode 100644
index 0000000..b0857d2
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiLayer.java
@@ -0,0 +1,1302 @@
+/*
+ * 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.wifi;
+
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
+
+import com.android.settings.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
+import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Helper class for abstracting Wi-Fi.
+ * <p>
+ * Client must call {@link #onCreate()}, {@link #onCreatedCallback()},
+ * {@link #onPause()}, {@link #onResume()}.
+ */
+public class WifiLayer {
+    
+    private static final String TAG = "SettingsWifiLayer";
+    static final boolean LOGV = false || Config.LOGV;
+    
+    //============================
+    // Other member variables
+    //============================
+    
+    private Context mContext;
+    private Callback mCallback;
+
+    static final int MESSAGE_ATTEMPT_SCAN = 1;
+    private Handler mHandler = new MyHandler();
+    
+    //============================
+    // Wifi member variables
+    //============================
+    
+    private WifiManager mWifiManager;
+    private IntentFilter mIntentFilter;
+    private List<AccessPointState> mApScanList = new ArrayList<AccessPointState>();
+    private List<AccessPointState> mApOtherList = new ArrayList<AccessPointState>();
+    private AccessPointState mCurrentPrimaryAp;
+    
+    /** The last access point that we were authenticating with. */
+    private AccessPointState mLastAuthenticatingAp;
+    
+    /** The delay between scans when we're continually scanning. */
+    private static final int CONTINUOUS_SCAN_DELAY_MS = 6000; 
+    /** On failure, the maximum retries for scanning. */
+    private static final int SCAN_MAX_RETRY = 5;
+    /** On failure, the delay between each scan retry. */
+    private static final int SCAN_RETRY_DELAY_MS = 1000;
+    /** On failure, the number of retries so far. */
+    private int mScanRetryCount = 0;
+    /**
+     * Whether we're currently obtaining an address. Continuous scanning will be
+     * disabled in this state.
+     */
+    private boolean mIsObtainingAddress;
+
+    /**
+     * See {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT}.
+     */
+    private int WIFI_NUM_OPEN_NETWORKS_KEPT;
+    /**
+     * Once the highest priority exceeds this value, all networks will be
+     * wrapped around starting at 0. This is so another client of the Wi-Fi
+     * API can have access points that aren't managed by us. (If the other
+     * client wants lower-priority access points than ours, it can use negative
+     * priority.)
+     */
+    private static final int HIGHEST_PRIORITY_MAX_VALUE = 99999;
+    /**
+     * Never access directly, only the related methods should.
+     */
+    private int mNextHighestPriority;
+    
+    /**
+     * This is used to track when the user wants to connect to a specific AP. We
+     * disable all other APs, set this to true, and let wpa_supplicant connect.
+     * Once we get a network state change, we re-enable the rest of them.
+     */
+    private boolean mReenableApsOnNetworkStateChange = false;
+
+    /**
+     * The current supplicant state, as broadcasted.
+     */
+    private SupplicantState mCurrentSupplicantState;
+    
+    //============================
+    // Inner classes
+    //============================
+    
+    interface Callback {
+        void onError(int messageResId);
+        
+        /**
+         * Called when an AP is added or removed.
+         * 
+         * @param ap The AP.
+         * @param added {@code true} if added, {@code false} if removed.
+         */
+        void onAccessPointSetChanged(AccessPointState ap, boolean added);
+        
+        /**
+         * Called when the scanning status changes.
+         * 
+         * @param started {@code true} if the scanning just started,
+         *            {@code false} if it just ended.
+         */
+        void onScanningStatusChanged(boolean started);
+
+        /**
+         * Called when the access points should be enabled or disabled. This is
+         * called from both wpa_supplicant being connected/disconnected and Wi-Fi
+         * being enabled/disabled.
+         * 
+         * @param enabled {@code true} if they should be enabled, {@code false}
+         *            if they should be disabled.
+         */
+        void onAccessPointsStateChanged(boolean enabled);   
+        
+        /**
+         * Called when there is trouble authenticating and the retry-password
+         * dialog should be shown.
+         * 
+         * @param ap The access point.
+         */
+        void onRetryPassword(AccessPointState ap);
+    }
+    
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                handleNetworkStateChanged(
+                        (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO),
+                        intent.getStringExtra(WifiManager.EXTRA_BSSID));
+            } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                handleScanResultsAvailable();
+            } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
+                handleSupplicantConnectionChanged(
+                        intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false));
+            } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
+                handleSupplicantStateChanged(
+                        (SupplicantState) intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE),
+                        intent.hasExtra(WifiManager.EXTRA_SUPPLICANT_ERROR),
+                        intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0));
+            } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                        WifiManager.WIFI_STATE_UNKNOWN));
+            } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+                handleSignalChanged(intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, 0));
+            } else if (action.equals(WifiManager.NETWORK_IDS_CHANGED_ACTION)) {
+                handleNetworkIdsChanged();
+            }
+        }
+    };
+    
+    /**
+     * If using this class, make sure to call the callbacks of this class, such
+     * as {@link #onCreate()}, {@link #onCreatedCallback()},
+     * {@link #onPause()}, {@link #onResume()}.
+     * 
+     * @param context The context.
+     * @param callback The interface that will be invoked when events from this
+     *            class are generated.
+     */
+    public WifiLayer(Context context, Callback callback) {
+        mContext = context;
+        mCallback = callback;
+    }
+    
+    //============================
+    // Lifecycle
+    //============================
+
+    /**
+     * The client MUST call this.
+     * <p>
+     * This shouldn't have any dependency on the callback.
+     */
+    public void onCreate() {
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        
+        mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+        mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+        mIntentFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
+        
+        WIFI_NUM_OPEN_NETWORKS_KEPT = Settings.Secure.getInt(mContext.getContentResolver(),
+            Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT, 10);
+    }
+    
+    /**
+     * The client MUST call this.
+     * <p>
+     * Callback is ready, this can do whatever it wants with it.
+     */
+    public void onCreatedCallback() {
+        if (isWifiEnabled()) {
+            refreshAll(false);
+        }
+    }
+    
+    /**
+     * The client MUST call this.
+     * 
+     * @see android.app.Activity#onResume
+     */
+    public void onResume() {
+        mContext.registerReceiver(mReceiver, mIntentFilter);
+        
+        if (isWifiEnabled()) {
+            // Kick start the continual scan
+            queueContinuousScan();
+        }
+    }
+
+    /**
+     * The client MUST call this.
+     * 
+     * @see android.app.Activity#onPause
+     */
+    public void onPause() {
+        mContext.unregisterReceiver(mReceiver);
+        
+        attemptReenableAllAps();
+        
+        removeFutureScans();
+    }
+    
+    //============================
+    // "Public" API
+    //============================
+
+    /**
+     * Returns an AccessPointState instance (that we track locally in WifiLayer)
+     * for the given state. First, we check if we track the given instance. If
+     * not, we find an equal AccessPointState instance that we track.
+     * 
+     * @param state An AccessPointState instance that does not necessarily have
+     *            to be one that this WifiLayer class tracks. For example, it
+     *            could be the result of unparceling.
+     * @return An AccessPointState instance that this WifiLayer class tracks.
+     */
+    public AccessPointState getWifiLayerApInstance(AccessPointState state) {
+        synchronized (this) {
+            
+            if (hasApInstanceLocked(state)) {
+                return state;
+            }
+            
+            return findApLocked(state.networkId, state.bssid, state.ssid, state.security);
+        }
+    }
+    
+    /**
+     * Connects to the network, and creates the Wi-Fi API config if necessary.
+     * 
+     * @param state The state of the network to connect to. This MUST be an
+     *            instance that was given to you by this class. If you
+     *            constructed the instance yourself (for example, after
+     *            unparceling it), you should use
+     *            {@link #getWifiLayerApInstance(AccessPointState)}.
+     * @return Whether the operation was successful.
+     */
+    public boolean connectToNetwork(AccessPointState state) {
+        if (LOGV) {
+            Log.v(TAG, "Connecting to " + state);
+        }
+        
+        // Need WifiConfiguration for the AP
+        WifiConfiguration config = findConfiguredNetwork(state);
+
+        if (LOGV) {
+            Log.v(TAG, " Found configured network " + config);
+        }
+        
+        if (config == null) {
+            /*
+             * Connecting for the first time, need to create it. We will enable
+             * and save it below (when we set priority).
+             */
+            config = addConfiguration(state, 0);
+
+            if (config == null) {
+                Log.e(TAG, "Config is still null, even after attempting to add it.");
+                error(R.string.error_connecting);
+                return false;
+            }
+
+            /*
+             * We could reload the configured networks, but instead just
+             * shortcut and add this state to our list in memory.
+             */
+            ensureTrackingState(state);
+        } else {
+            // Make sure the configuration has the latest from the state
+            state.updateWifiConfiguration(config);
+        }
+
+        // Enable this network before we save to storage
+        if (!managerEnableNetwork(state, false)) {
+            Log.e(TAG, "Could not enable network ID " + state.networkId);
+            error(R.string.error_connecting);
+            return false;
+        }
+        
+        /*
+         * Give it highest priority, this could cause a network ID change, so do
+         * it after any modifications to the network we're connecting to
+         */
+        setHighestPriorityStateAndSave(state, config);
+        
+        /*
+         * We force supplicant to connect to this network by disabling the
+         * others. We do this AFTER we save above so this disabled flag isn't
+         * persisted.
+         */
+        mReenableApsOnNetworkStateChange = true;
+        if (!managerEnableNetwork(state, true)) {
+            Log.e(TAG, "Could not enable network ID " + state.networkId);
+            error(R.string.error_connecting);
+            return false;
+        }
+
+        if (LOGV) {
+            Log.v(TAG, " Enabled network " + state.networkId);
+        }
+
+        if (mCurrentSupplicantState == SupplicantState.DISCONNECTED ||
+                mCurrentSupplicantState == SupplicantState.SCANNING) {
+            mWifiManager.reconnect();
+        }
+        
+        // Check for too many configured open networks
+        if (!state.hasSecurity()) {
+            checkForExcessOpenNetworks();
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Saves a network, and creates the Wi-Fi API config if necessary.
+     * 
+     * @param state The state of the network to save. If you constructed the
+     *            instance yourself (for example, after unparceling it), you
+     *            should use {@link #getWifiLayerApInstance(AccessPointState)}.
+     * @return Whether the operation was successful.
+     */
+    public boolean saveNetwork(AccessPointState state) {
+        WifiConfiguration config = findConfiguredNetwork(state);
+        
+        if (config == null) {
+            // if the user is adding a new network, assume that it is hidden
+            state.setHiddenSsid(true);
+
+            config = addConfiguration(state, ADD_CONFIGURATION_ENABLE);
+
+            if (config == null) {
+                Log.e(TAG, "Could not save configuration, call to addConfiguration failed.");
+                error(R.string.error_saving);
+                return false;
+            }
+            
+        } else {
+            state.updateWifiConfiguration(config);
+            if (mWifiManager.updateNetwork(config) == -1) {
+                Log.e(TAG, "Could not update configuration, call to WifiManager failed.");
+                error(R.string.error_saving);
+                return false;
+            }
+        }
+        
+        // Successfully added network, go ahead and persist
+        if (!managerSaveConfiguration()) {
+            Log.e(TAG, "Could not save configuration, call to WifiManager failed.");
+            error(R.string.error_saving);
+            return false;
+        }
+        
+        /*
+         * We could reload the configured networks, but instead just shortcut
+         * and add this state to our list in memory
+         */
+        ensureTrackingState(state);
+        
+        return true;
+    }
+    
+    /**
+     * Forgets a network.
+     * 
+     * @param state The state of the network to forget. If you constructed the
+     *            instance yourself (for example, after unparceling it), you
+     *            should use {@link #getWifiLayerApInstance(AccessPointState)}.
+     * @return Whether the operation was succesful.
+     */
+    public boolean forgetNetwork(AccessPointState state) {
+        if (!state.configured) {
+            Log.w(TAG, "Inconsistent state:  Forgetting a network that is not configured.");
+            return true;
+        }
+        
+        int oldNetworkId = state.networkId;
+        state.forget();
+        
+        if (!state.seen) {
+            // If it is not seen, it should be removed from the UI
+            removeApFromUi(state);
+        }
+                    
+        synchronized (this) {
+            mApOtherList.remove(state);
+            // It should not be removed from the scan list, since if it was
+            // there that means it's still seen
+        }
+
+        if (!mWifiManager.removeNetwork(oldNetworkId)) {
+            Log.e(TAG, "Removing network " + state.ssid + " (network ID " + oldNetworkId +
+                    ") failed.");
+            return false;
+        }
+        
+        if (!managerSaveConfiguration()) {
+            error(R.string.error_saving);
+            return false;
+        }
+
+        return true;
+    }
+    
+    /**
+     * This ensures this class is tracking the given state. This means it is in
+     * our list of access points, either in the scanned list or in the
+     * remembered list.
+     * 
+     * @param state The state that will be checked for tracking, and if not
+     *            tracking will be added to the remembered list in memory.
+     */
+    private void ensureTrackingState(AccessPointState state) {
+        synchronized (this) {
+            if (hasApInstanceLocked(state)) {
+                return;
+            }
+            
+            mApOtherList.add(state);
+        }
+    }
+    
+    /**
+     * Attempts to scan networks.  This has a retry mechanism.
+     */
+    public void attemptScan() {
+        
+        // Remove any future scans since we're scanning right now
+        removeFutureScans();
+        
+        if (!mWifiManager.isWifiEnabled()) return;
+        
+        if (!mWifiManager.startScan()) {
+            postAttemptScan();
+        } else {
+            mScanRetryCount = 0;
+        }
+    }
+
+    private void queueContinuousScan() {
+        mHandler.removeMessages(MESSAGE_ATTEMPT_SCAN);
+        
+        if (!mIsObtainingAddress) {
+            // Don't do continuous scan while in obtaining IP state
+            mHandler.sendEmptyMessageDelayed(MESSAGE_ATTEMPT_SCAN, CONTINUOUS_SCAN_DELAY_MS);
+        }
+    }
+    
+    private void removeFutureScans() {
+        mHandler.removeMessages(MESSAGE_ATTEMPT_SCAN);
+    }
+    
+    public boolean isWifiEnabled() {
+        return mWifiManager.isWifiEnabled();
+    }
+    
+    public void error(int messageResId) {
+        Log.e(TAG, mContext.getResources().getString(messageResId));
+        
+        if (mCallback != null) {
+            mCallback.onError(messageResId);
+        }
+    }
+    
+    //============================
+    // Wifi logic
+    //============================
+
+    private void refreshAll(boolean attemptScan) {
+        loadConfiguredAccessPoints();
+        refreshStatus();
+        
+        if (attemptScan) {
+            attemptScan();
+        }
+    }
+    
+    private void postAttemptScan() {
+        onScanningStarted();
+
+        if (++mScanRetryCount < SCAN_MAX_RETRY) {
+            // Just in case, remove previous ones first
+            removeFutureScans();
+            mHandler.sendEmptyMessageDelayed(MESSAGE_ATTEMPT_SCAN, SCAN_RETRY_DELAY_MS);
+        } else {
+            // Show an error once we run out of attempts
+            error(R.string.error_scanning);
+            onScanningEnded();
+        }
+    }
+    
+    private void onScanningStarted() {
+        if (mCallback != null) {
+            mCallback.onScanningStatusChanged(true);
+        }
+    }
+    
+    private void onScanningEnded() {
+        queueContinuousScan();
+        
+        if (mCallback != null) {
+            mCallback.onScanningStatusChanged(false);
+        }
+    }
+    
+    private void clearApLists() {
+        List<AccessPointState> accessPoints = new ArrayList<AccessPointState>();
+        
+        synchronized(this) {
+            // Clear the logic's list of access points
+            accessPoints.addAll(mApScanList);
+            accessPoints.addAll(mApOtherList);
+            mApScanList.clear();
+            mApOtherList.clear();
+        }
+        
+        for (int i = accessPoints.size() - 1; i >= 0; i--) {
+            removeApFromUi(accessPoints.get(i));
+        }
+    }
+    
+    private boolean managerSaveConfiguration() {
+        boolean retValue = mWifiManager.saveConfiguration();
+        
+        /*
+         * We need to assume the network IDs have changed, so handle this. Note:
+         * we also have a receiver on the broadcast intent in case another wifi
+         * framework client caused the change. In this case, we will handle the
+         * possible network ID change twice (but it's not too costly).
+         */
+        handleNetworkIdsChanged();
+        
+        return retValue;
+    }
+    
+    private boolean managerEnableNetwork(AccessPointState state, boolean disableOthers) {
+        if (!mWifiManager.enableNetwork(state.networkId, disableOthers)) {
+            return false;
+        }
+        
+        // Enabling was successful, make sure the state is not disabled
+        state.setDisabled(false);
+        
+        return true;
+    }
+    
+    private static final int ADD_CONFIGURATION_ENABLE = 1;
+    private static final int ADD_CONFIGURATION_SAVE = 2;
+    private WifiConfiguration addConfiguration(AccessPointState state, int flags) {
+        // Create and add
+        WifiConfiguration config = new WifiConfiguration();
+        
+        state.updateWifiConfiguration(config);
+        
+        final int networkId = mWifiManager.addNetwork(config);
+        if (networkId == -1) {
+            return null;
+        }
+        
+        state.setNetworkId(networkId);
+        state.setConfigured(true);
+        
+        // If we should, then enable it, since it comes disabled by default
+        if ((flags & ADD_CONFIGURATION_ENABLE) != 0
+                && !managerEnableNetwork(state, false)) {
+            return null;
+        }
+
+        // If we should, then save it
+        if ((flags & ADD_CONFIGURATION_SAVE) != 0 && !managerSaveConfiguration()) {
+            return null;
+        }
+
+        if (mCallback != null) {
+            mCallback.onAccessPointSetChanged(state, true);
+        }
+
+        return config;
+    }
+    
+    private WifiConfiguration findConfiguredNetwork(AccessPointState state) {
+        final List<WifiConfiguration> wifiConfigs = getConfiguredNetworks();
+        
+        for (int i = wifiConfigs.size() - 1; i >= 0; i--) {
+            final WifiConfiguration wifiConfig = wifiConfigs.get(i); 
+            if (state.matchesWifiConfiguration(wifiConfig) >= AccessPointState.MATCH_WEAK) {
+                return wifiConfig;
+            }
+        }
+        
+        return null;
+    }
+    
+    private List<WifiConfiguration> getConfiguredNetworks() {
+        final List<WifiConfiguration> wifiConfigs = mWifiManager.getConfiguredNetworks();
+        return wifiConfigs;
+    }
+    
+    /**
+     * Must call while holding the lock for the list, which is usually the
+     * WifiLayer instance.
+     */
+    private static AccessPointState findApLocked(List<AccessPointState> list, int networkId,
+            String bssid, String ssid, String security) {
+        AccessPointState ap;
+        for (int i = list.size() - 1; i >= 0; i--) {
+            ap = list.get(i);
+            if (ap.matches(networkId, bssid, ssid, security) >= AccessPointState.MATCH_WEAK) {
+                return ap;
+            }
+        }
+
+        return null;
+    }
+    
+    /**
+     * Must call while holding the lock for the lists, which is usually this
+     * WifiLayer instance.
+     */
+    private AccessPointState findApLocked(int networkId, String bssid, String ssid,
+            String security) {
+        AccessPointState ap = findApLocked(mApScanList, networkId, bssid, ssid, security);
+        if (ap == null) {
+            ap = findApLocked(mApOtherList, networkId, bssid, ssid, security);
+        }
+        return ap;
+    }
+
+    /**
+     * Returns whether we have the exact instance of the access point state
+     * given. This is useful in cases where an AccessPointState has been
+     * parceled by the client and the client is attempting to use it to
+     * connect/forget/save.
+     * <p>
+     * Must call while holding the lock for the lists, which is usually this
+     * WifiLayer instance.
+     */
+    private boolean hasApInstanceLocked(AccessPointState state) {
+        
+        for (int i = mApScanList.size() - 1; i >= 0; i--) {
+            if (mApScanList.get(i) == state) {
+                return true;
+            }
+        }
+
+        for (int i = mApOtherList.size() - 1; i >= 0; i--) {
+            if (mApOtherList.get(i) == state) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    private void loadConfiguredAccessPoints() {
+        final List<WifiConfiguration> configs = getConfiguredNetworks();
+        
+        for (int i = configs.size() - 1; i >= 0; i--) {
+            final WifiConfiguration config = configs.get(i);
+            
+            AccessPointState ap;
+            synchronized(this) {
+                ap = findApLocked(config.networkId, config.BSSID, config.SSID,
+                        AccessPointState.getWifiConfigurationSecurity(config));
+                
+                if (ap != null) {
+                    // We already know about this one
+                    continue;
+                }
+    
+                ap = new AccessPointState(mContext);
+                ap.updateFromWifiConfiguration(config);
+                if (LOGV) Log.v(TAG, "Created " + ap + " in loadConfiguredAccessPoints");
+                mApOtherList.add(ap);
+            }
+
+            // Make sure our next highest priority is greater than this
+            checkNextHighestPriority(ap.priority);
+            
+            if (mCallback != null) {
+                mCallback.onAccessPointSetChanged(ap, true);
+            }
+        }
+    }
+
+    private AccessPointState getCurrentAp() {
+        final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        
+        String ssid = wifiInfo.getSSID();
+        if (ssid != null) {
+            /*
+             * We pass null for security since we have a network ID (i.e., it's
+             * not a wildcard), and rely on it matching.
+             */
+            return findApLocked(wifiInfo.getNetworkId(), wifiInfo.getBSSID(), ssid, null);
+        } else {
+            return null;
+        }
+    }
+    
+    private void setPrimaryAp(AccessPointState ap) {
+        synchronized (this) {
+            // Unset other
+            if (mCurrentPrimaryAp != null) {
+                mCurrentPrimaryAp.setPrimary(false);
+            }
+
+            mCurrentPrimaryAp = ap;
+        }
+
+        if (ap != null) {
+            ap.setPrimary(true);
+        }
+    }
+
+    private void attemptReenableAllAps() {
+        if (mReenableApsOnNetworkStateChange) {
+            mReenableApsOnNetworkStateChange = false;
+            enableAllAps();
+        }
+    }
+    
+    private void enableAllAps() {
+        synchronized(this) {
+            if (LOGV) {
+                Log.v(TAG, " Enabling all APs");
+            }
+            
+            enableApsLocked(mApOtherList);
+            enableApsLocked(mApScanList);
+        }
+    }
+    
+    private void enableApsLocked(List<AccessPointState> apList) {
+        for (int i = apList.size() - 1; i >= 0; i--) {
+            AccessPointState state = apList.get(i);
+            int networkId = state.networkId;
+            if (networkId != AccessPointState.NETWORK_ID_NOT_SET &&
+                    networkId != AccessPointState.NETWORK_ID_ANY) {
+                managerEnableNetwork(state, false);
+            }
+        }
+    }
+    
+    private void removeApFromUi(AccessPointState ap) {
+        if (mCallback != null) {
+            mCallback.onAccessPointSetChanged(ap, false);
+        }
+    }
+    
+    /**
+     * Sets the access point state to the highest priority.
+     * <p>
+     * If you have a list of configured networks from WifiManager, you probably
+     * shouldn't call this until you're done traversing the list.
+     * 
+     * @param state The state to set as the highest priority.
+     * @param reusableConfiguration An optional WifiConfiguration that will be
+     *            given to the WifiManager as updated data for the network ID.
+     *            This will be filled with the new priority.
+     * @return Whether the operation was successful.
+     */
+    private boolean setHighestPriorityStateAndSave(AccessPointState state,
+            WifiConfiguration reusableConfiguration) {
+
+        if (!isConsideredForHighestPriority(state)) {
+            Log.e(TAG,
+                    "Could not set highest priority on state because state is not being considered.");
+            return false;
+        }
+        
+        if (reusableConfiguration == null) {
+            reusableConfiguration = new WifiConfiguration();
+        }
+        
+        int oldPriority = reusableConfiguration.priority;
+        reusableConfiguration.priority = getNextHighestPriority();
+        reusableConfiguration.networkId = state.networkId;
+        
+        if (mWifiManager.updateNetwork(reusableConfiguration) == -1) {
+            // Rollback priority
+            reusableConfiguration.priority = oldPriority;
+            Log.e(TAG,
+                    "Could not set highest priority on state because updating the supplicant network failed.");
+            return false;
+        }
+
+        if (!managerSaveConfiguration()) {
+            reusableConfiguration.priority = oldPriority;
+            Log.e(TAG,
+                    "Could not set highest priority on state because saving config failed.");
+            return false;
+        }
+        
+        state.priority = reusableConfiguration.priority;
+        
+        if (LOGV) {
+            Log.v(TAG, " Set highest priority to "
+                    + state.priority + " from " + oldPriority);
+        }
+        
+        return true;
+    }
+
+    /**
+     * Makes sure the next highest priority is larger than the given priority.
+     */
+    private void checkNextHighestPriority(int priority) {
+        if (priority > HIGHEST_PRIORITY_MAX_VALUE || priority < 0) {
+            // This is a priority that we aren't managing
+            return;
+        }
+        
+        if (mNextHighestPriority <= priority) {
+            mNextHighestPriority = priority + 1;
+        }
+    }
+
+    /**
+     * Checks if there are too many open networks, and removes the excess ones.
+     */
+    private void checkForExcessOpenNetworks() {
+        synchronized(this) {
+            ArrayList<AccessPointState> allAps = getApsSortedByPriorityLocked();
+
+            // Walk from highest to lowest priority
+            int openConfiguredCount = 0;
+            for (int i = allAps.size() - 1; i >= 0; i--) {
+                AccessPointState state = allAps.get(i);
+                if (state.configured && !state.hasSecurity()) {
+                    openConfiguredCount++;
+                    if (openConfiguredCount > WIFI_NUM_OPEN_NETWORKS_KEPT) {
+                        // Remove this network
+                        forgetNetwork(state);
+                    }
+                }
+            }
+        }
+    }
+    
+    private boolean isConsideredForHighestPriority(AccessPointState state) {
+        return state.configured && state.networkId != AccessPointState.NETWORK_ID_ANY &&
+                state.networkId != AccessPointState.NETWORK_ID_NOT_SET;
+    }
+    
+    /**
+     * Gets the next highest priority. If this value is larger than the max,
+     * shift all the priorities so the lowest starts at 0.
+     * <p>
+     * Only
+     * {@link #setHighestPriorityStateAndSave(AccessPointState, WifiConfiguration)}
+     * should call this.
+     * 
+     * @return The next highest priority to use.
+     */
+    private int getNextHighestPriority() {
+        if (mNextHighestPriority > HIGHEST_PRIORITY_MAX_VALUE) {
+            shiftPriorities();
+        }
+        
+        return mNextHighestPriority++;
+    }
+
+    /**
+     * Shift all the priorities so the lowest starts at 0.
+     * 
+     * @return Whether the operation was successful.
+     */
+    private boolean shiftPriorities() {
+        synchronized(this) {
+
+            ArrayList<AccessPointState> allAps = getApsSortedByPriorityLocked();
+
+            // Re-usable WifiConfiguration for setting priority
+            WifiConfiguration updatePriorityConfig = new WifiConfiguration();
+            
+            // Set new priorities
+            mNextHighestPriority = 0;
+            int size = allAps.size();
+            for (int i = 0; i < size; i++) {
+                AccessPointState state = allAps.get(i);
+                
+                if (!isConsideredForHighestPriority(state)) {
+                    continue;
+                }
+                
+                if (!setHighestPriorityStateAndSave(state, updatePriorityConfig)) {
+                    Log.e(TAG,
+                            "Could not shift priorities because setting the new priority failed.");
+                    return false;
+                }
+            }
+            
+            return true;
+        }
+    }
+
+    private ArrayList<AccessPointState> getApsSortedByPriorityLocked() {
+        // Get all of the access points we have
+        ArrayList<AccessPointState> allAps = new ArrayList<AccessPointState>(mApScanList.size()
+                + mApOtherList.size());
+        allAps.addAll(mApScanList);
+        allAps.addAll(mApOtherList);
+        
+        // Sort them based on priority
+        Collections.sort(allAps, new Comparator<AccessPointState>() {
+            public int compare(AccessPointState object1, AccessPointState object2) {
+                return object1.priority - object2.priority;
+            }
+        });
+
+        return allAps;
+    }
+    
+    //============================
+    // Status related
+    //============================
+
+    private void refreshStatus() {
+        refreshStatus(null, null);
+    }
+    
+    private void refreshStatus(AccessPointState ap, NetworkInfo.DetailedState detailedState) {
+        final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        if (detailedState == null) {
+            detailedState = WifiInfo.getDetailedStateOf(wifiInfo.getSupplicantState());
+        }
+
+        if (ap == null && WifiStatus.isLiveConnection(detailedState)) {
+            /*
+             * We pass null for security since we have a network ID (i.e., it's
+             * not a wildcard), and rely on it matching.
+             */
+            ap = findApLocked(wifiInfo.getNetworkId(), wifiInfo.getBSSID(), wifiInfo
+                    .getSSID(), null);
+        }
+
+        if (ap != null) {
+            ap.blockRefresh();
+
+            // Let the AP get the latest info from the WifiInfo 
+            ap.updateFromWifiInfo(wifiInfo, detailedState);
+
+            // The detailed state from the Intent has more states than the WifiInfo's detailed
+            // state can have (for example, DHCP completion).  Set the status using
+            // the Intent's detailed state.
+            ap.setStatus(detailedState);
+            ap.unblockRefresh();
+        }
+    }
+    
+    //============================
+    // Wifi callbacks
+    //============================
+    
+    private void handleNetworkStateChanged(NetworkInfo info, String bssid) {
+        final AccessPointState ap = getCurrentAp();
+        NetworkInfo.DetailedState detailedState = info.getDetailedState(); 
+
+        if (LOGV) {
+            Log.v(TAG, "State change received " + info.toString() + ", or "
+                    + detailedState + " on " + bssid + " matched to " + ap);
+        }
+
+        handleDisablingScanWhileObtainingAddress(detailedState);
+        
+        // This will update the AP with its new info
+        refreshStatus(ap, detailedState);
+        
+        boolean isDisconnected = info.getState().equals(State.DISCONNECTED);
+        if (ap != null && info.isConnectedOrConnecting()) {
+            setPrimaryAp(ap);
+
+            if (LOGV) {
+                Log.v(TAG, " Updated " + ap + " to be primary");
+            }
+            
+        } else if (isDisconnected) {
+            
+            /*
+             * When we drop off a network (for example, the router is powered
+             * down when we were connected), we received a DISCONNECT event
+             * without a BSSID. We should not have a primary AP anymore.
+             */
+            setPrimaryAp(null);
+            
+            if (LOGV) {
+                Log.v(TAG, " Cleared primary");
+            }
+            
+        } else if (detailedState.equals(DetailedState.FAILED)) {
+
+            /*
+             * Doh, failed for whatever reason. Unset the primary AP, but set
+             * failed status on the AP that failed.
+             */
+            setPrimaryAp(null);
+            ap.setStatus(DetailedState.FAILED);
+            
+            // Bring up error dialog
+            error(R.string.wifi_generic_connection_error);
+            
+        } else if (LOGV) {
+            Log.v(TAG, " Did not update any AP to primary, could have updated "
+                    + ap + " but we aren't connected or connecting");
+        }
+
+        if ((ap != null) && (info.isConnected()
+                    || (detailedState == DetailedState.OBTAINING_IPADDR))) {
+            /*
+             * Sometimes the scan results do not contain the AP even though it's
+             * clearly connected. This may be because we do passive background
+             * scanning that isn't as 'strong' as active scanning, so even
+             * though a network is nearby, it won't be seen by the passive
+             * scanning. If we are connected (or obtaining IP) then we know it
+             * is seen.
+             */
+            ap.setSeen(true);
+        }
+
+        attemptReenableAllAps();
+    }
+
+    private void handleDisablingScanWhileObtainingAddress(DetailedState detailedState) {
+        
+        if (detailedState == DetailedState.OBTAINING_IPADDR) {
+            mIsObtainingAddress = true;
+
+            // We will not scan while obtaining an IP address
+            removeFutureScans();
+            
+        } else {
+            mIsObtainingAddress = false;
+            
+            // Start continuous scan
+            queueContinuousScan();
+        }
+    }
+    
+    private void handleScanResultsAvailable() {
+        synchronized(this) {
+            // In the end, we'll moved the ones no longer seen into the mApOtherList
+            List<AccessPointState> oldScanList = mApScanList;
+            List<AccessPointState> newScanList =
+                    new ArrayList<AccessPointState>(oldScanList.size());
+
+            List<ScanResult> list = mWifiManager.getScanResults();
+            if (list != null) {
+                for (int i = list.size() - 1; i >= 0; i--) {
+                    final ScanResult scanResult = list.get(i);
+                    
+                    if (LOGV) {
+//                        Log.v(TAG, "    " + scanResult);
+                    }
+                    
+                    if (scanResult == null) {
+                        continue;
+                    }
+                    
+                    /*
+                     * Ignore adhoc, enterprise-secured, or hidden networks.
+                     * Hidden networks show up with empty SSID.
+                     */
+                    if (AccessPointState.isAdhoc(scanResult)
+                            || AccessPointState.isEnterprise(scanResult)
+                            || TextUtils.isEmpty(scanResult.SSID)) {
+                        continue;
+                    }
+                    
+                    final String ssid = AccessPointState.convertToQuotedString(scanResult.SSID);
+                    String security = AccessPointState.getScanResultSecurity(scanResult);
+                    
+                    // See if this AP is part of a group of APs (e.g., any large
+                    // wifi network has many APs, we'll only show one) that we've
+                    // seen in this scan
+                    AccessPointState ap = findApLocked(newScanList, AccessPointState.NETWORK_ID_ANY,
+                                                 AccessPointState.BSSID_ANY, ssid, security);
+
+                    // Yup, we've seen this network.
+                    if (ap != null) {
+                        // Use the better signal
+                        if (WifiManager.compareSignalLevel(scanResult.level, ap.signal) > 0) {
+                            ap.setSignal(scanResult.level);
+                        }
+                        
+                        if (LOGV) {
+//                            Log.v(TAG, "         Already seen, continuing..");
+                        }
+                        
+                        continue;
+                    }
+
+                    // Find the AP in either our old scan list, or our non-seen
+                    // configured networks list
+                    ap = findApLocked(AccessPointState.NETWORK_ID_ANY, AccessPointState.BSSID_ANY,
+                                ssid, security);
+
+                    if (ap != null) {
+                        // Remove the AP from both (no harm if one doesn't contain it)
+                        oldScanList.remove(ap);
+                        mApOtherList.remove(ap);
+                    } else {
+                        ap = new AccessPointState(mContext);
+//                        if (LOGV) Log.v(TAG, "Created " + ap);
+                    }
+
+                    // Give it the latest state
+                    ap.updateFromScanResult(scanResult);
+
+                    if (mCallback != null) {
+                        mCallback.onAccessPointSetChanged(ap, true);
+                    }
+
+                    newScanList.add(ap);
+                }
+            }
+            
+            // oldScanList contains the ones no longer seen
+            List<AccessPointState> otherList = mApOtherList;
+            for (int i = oldScanList.size() - 1; i >= 0; i--) {
+                final AccessPointState ap = oldScanList.get(i);
+                
+                if (ap.configured) {
+                    
+                    // Keep it around, since it is configured
+                    ap.setSeen(false);
+                    otherList.add(ap);
+                    
+                } else {
+                    
+                    // Remove it since it is not configured and not seen
+                    removeApFromUi(ap);
+                }
+            }
+            
+            mApScanList = newScanList;
+        }
+
+        onScanningEnded();
+    }
+    
+    private void handleSupplicantConnectionChanged(boolean connected) {
+        if (mCallback != null) {
+            mCallback.onAccessPointsStateChanged(connected);
+        }
+        
+        if (connected) {
+            refreshAll(true);
+        }
+    }
+
+    private void handleWifiStateChanged(int wifiState) {
+        
+        if (wifiState == WIFI_STATE_ENABLED) {
+            loadConfiguredAccessPoints();
+            attemptScan();
+
+        } else if (wifiState == WIFI_STATE_DISABLED) {
+            removeFutureScans();
+            if (LOGV) Log.v(TAG, "Clearing AP lists because wifi is disabled");
+            clearApLists();
+        }
+        
+        if (mCallback != null) {
+            mCallback.onAccessPointsStateChanged(wifiState == WIFI_STATE_ENABLED);
+        }
+    }
+    
+    private void handleSignalChanged(int rssi) {
+        
+        if (mCurrentPrimaryAp != null) {
+            mCurrentPrimaryAp.setSignal(rssi);
+        }
+    }
+
+    private void handleSupplicantStateChanged(SupplicantState state, boolean hasError, int error) {
+        mCurrentSupplicantState = state;
+        
+        if (SupplicantState.FOUR_WAY_HANDSHAKE.equals(state)) {
+            mLastAuthenticatingAp = getCurrentAp();
+        }
+        
+        if (hasError) {
+            handleSupplicantStateError(error);
+        }
+    }
+    
+    private void handleSupplicantStateError(int supplicantError) {
+        if (supplicantError == WifiManager.ERROR_AUTHENTICATING) {
+            if (mCallback != null) {
+                if (mLastAuthenticatingAp != null) {
+                    mCallback.onRetryPassword(mLastAuthenticatingAp);
+                }
+            }
+        }
+    }
+    
+    private void handleNetworkIdsChanged() {
+        synchronized (this) {
+            final List<WifiConfiguration> configs = getConfiguredNetworks();
+            
+            for (int i = configs.size() - 1; i >= 0; i--) {
+                final WifiConfiguration config = configs.get(i);
+                
+                AccessPointState ap;
+                // Since network IDs have changed, we can't use it to find our previous AP state
+                ap = findApLocked(AccessPointState.NETWORK_ID_ANY, config.BSSID, config.SSID,
+                        AccessPointState.getWifiConfigurationSecurity(config));
+                
+                if (ap == null) {
+                    continue;
+                }
+
+                ap.setNetworkId(config.networkId);
+            }
+        }
+    }
+    
+    private class MyHandler extends Handler {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_ATTEMPT_SCAN:
+                    attemptScan();
+                    break;
+            }
+        }
+    }
+    
+}
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
new file mode 100644
index 0000000..d2683c0
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -0,0 +1,438 @@
+/*
+ * 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.wifi;
+
+import com.android.settings.ProgressCategory;
+import com.android.settings.R;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.preference.CheckBoxPreference;
+import android.provider.Settings;
+import android.util.Log;
+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;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * Settings screen for WiFi. This will be launched from the main system settings.
+ */
+public class WifiSettings extends PreferenceActivity implements WifiLayer.Callback,
+        DialogInterface.OnDismissListener {
+
+    private static final String TAG = "WifiSettings";
+    
+    //============================
+    // Preference/activity member variables
+    //============================
+    
+    private static final String INSTANCE_KEY_DIALOG_BUNDLE =
+            "com.android.settings.wifi.WifiSettings:dialogBundle";
+    /*
+     * We don't use Activity's dialog management because AlertDialog isn't fully
+     * able to change many of its features after it's been created, and the
+     * dialog management only creates once.
+     */
+    private Dialog mDialog;
+    
+    private static final String KEY_ADD_OTHER_NETWORK = "add_other_network";
+
+    private static final int CONTEXT_MENU_ID_CONNECT = Menu.FIRST;
+    private static final int CONTEXT_MENU_ID_FORGET = Menu.FIRST + 1;
+    private static final int CONTEXT_MENU_ID_CHANGE_PASSWORD = Menu.FIRST + 2;
+
+    private static final int MENU_ID_SCAN = Menu.FIRST; 
+    private static final int MENU_ID_ADVANCED = Menu.FIRST + 1; 
+    
+    private static final String KEY_WIFI_ENABLED = "wifi_enabled";
+    private static final String KEY_OPEN_NETWORK_NOTIFICATIONS_ENABLED =
+            "open_network_notifications_enabled";
+    private static final String KEY_ACCESS_POINTS = "access_points";
+    
+    private ProgressCategory mApCategory;
+    private CheckBoxPreference mWifiEnabled;
+    private WifiEnabler mWifiEnabler;
+    private CheckBoxPreference mOpenNetworkNotificationsEnabled; 
+    private Preference mAddOtherNetwork;
+    
+    private WeakHashMap<AccessPointState, AccessPointPreference> mAps;
+    
+    //============================
+    // Wifi member variables
+    //============================
+    
+    private WifiLayer mWifiLayer;
+        
+    //============================
+    // Activity lifecycle
+    //============================
+    
+    public WifiSettings() {
+        mAps = new WeakHashMap<AccessPointState, AccessPointPreference>();
+        mWifiLayer = new WifiLayer(this, this);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        onCreatePreferences();
+        mWifiLayer.onCreate();
+
+        onCreatedWifi();
+        mWifiLayer.onCreatedCallback();
+    }
+    
+    /**
+     * Shouldn't have any dependency on the wifi layer.
+     */
+    private void onCreatePreferences() {
+        addPreferencesFromResource(R.xml.wifi_settings);
+        
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        
+        mApCategory = (ProgressCategory) preferenceScreen.findPreference(KEY_ACCESS_POINTS);
+        // We don't want the ordering to be the order preferences are added,
+        // instead we want*:
+        //   1) preferred, visible APs
+        //   2) visible APs
+        //   3) preferred, APs out of range
+        //   * this ordering logic is in AccessPointPreference's compareTo
+        mApCategory.setOrderingAsAdded(false);
+        
+        mWifiEnabled = (CheckBoxPreference) preferenceScreen.findPreference(KEY_WIFI_ENABLED);
+        mWifiEnabler = new WifiEnabler(this, (WifiManager) getSystemService(WIFI_SERVICE),
+                mWifiEnabled);
+        
+        mOpenNetworkNotificationsEnabled = (CheckBoxPreference) preferenceScreen
+                .findPreference(KEY_OPEN_NETWORK_NOTIFICATIONS_ENABLED);
+        mOpenNetworkNotificationsEnabled.setChecked(Settings.Secure.getInt(getContentResolver(),
+            Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);        
+        
+        mAddOtherNetwork = preferenceScreen.findPreference(KEY_ADD_OTHER_NETWORK);
+        
+        registerForContextMenu(getListView());
+    }
+
+    private void onCreatedWifi() {
+    }
+    
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mWifiLayer.onResume();
+        mWifiEnabler.resume();
+    }
+    
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mWifiLayer.onPause();
+        mWifiEnabler.pause();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        
+        menu.add(0, MENU_ID_SCAN, 0, R.string.scan_wifi)
+            .setIcon(R.drawable.ic_menu_scan_network);
+        
+        menu.add(0, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
+            .setIcon(android.R.drawable.ic_menu_manage);
+        
+        return true;
+    }
+    
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        super.onOptionsItemSelected(item);
+        
+        switch (item.getItemId()) {
+
+            case MENU_ID_SCAN:
+                mWifiLayer.attemptScan();
+                return true;
+                
+            case MENU_ID_ADVANCED:
+                Intent intent = new Intent(this, AdvancedSettings.class);
+                startActivity(intent);
+                return true;
+                
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        
+        if (mDialog != null) {
+            Bundle dialogBundle = mDialog.onSaveInstanceState();
+            outState.putBundle(INSTANCE_KEY_DIALOG_BUNDLE, dialogBundle);
+        }
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle state) {
+        super.onRestoreInstanceState(state);
+        
+        Bundle dialogBundle = state.getBundle(INSTANCE_KEY_DIALOG_BUNDLE);
+        if (dialogBundle != null) {
+            mDialog = new AccessPointDialog(this, mWifiLayer);
+            mDialog.onRestoreInstanceState(dialogBundle);
+            showDialog(mDialog);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void onDismiss(DialogInterface dialog) {
+        if (dialog == mDialog) {
+            mDialog = null;
+        }
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+     
+        AccessPointState state = getStateFromMenuInfo(menuInfo);
+        if (state == null) {
+            return;
+        }
+     
+        menu.setHeaderTitle(state.getHumanReadableSsid());
+        
+        if (state.isConnectable()) {
+            menu.add(0, CONTEXT_MENU_ID_CONNECT, 0, R.string.wifi_context_menu_connect);
+        }
+        
+        if (state.isForgetable()) {
+            menu.add(0, CONTEXT_MENU_ID_FORGET, 1, R.string.wifi_context_menu_forget);
+            
+            if (state.hasPassword()) {
+                menu.add(0, CONTEXT_MENU_ID_CHANGE_PASSWORD, 2,
+                        R.string.wifi_context_menu_change_password);
+            }
+        }
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        
+        AccessPointState state = getStateFromMenuInfo(item.getMenuInfo());
+        if (state == null) {
+            return false;
+        }
+
+        switch (item.getItemId()) {
+            
+            case CONTEXT_MENU_ID_CONNECT:
+                connectToNetwork(state);
+                return true;
+                
+            case CONTEXT_MENU_ID_FORGET:
+                mWifiLayer.forgetNetwork(state);
+                return true;
+                
+            case CONTEXT_MENU_ID_CHANGE_PASSWORD:
+                showAccessPointDialog(state, AccessPointDialog.MODE_CONFIGURE);
+                return true;
+                
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Decides what needs to happen to connect to a particular access point. If
+     * it is secured and doesn't already have a password, it will bring up a
+     * password box. Otherwise it will just connect.
+     */
+    private void connectToNetwork(AccessPointState state) {
+        if (state.hasSecurity() && !state.hasPassword()) {
+            showAccessPointDialog(state, AccessPointDialog.MODE_INFO);
+        } else {
+            mWifiLayer.connectToNetwork(state);
+        }
+    }
+    
+    private AccessPointState getStateFromMenuInfo(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 AccessPointPreference)) {
+            return null;
+        }
+
+        return ((AccessPointPreference) pref).getAccessPointState();
+    }
+    
+    //============================
+    // Preference callbacks
+    //============================
+    
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        super.onPreferenceTreeClick(preferenceScreen, preference);
+
+        if (preference == mAddOtherNetwork) {
+            showAddOtherNetworkDialog();
+        } else if (preference == mOpenNetworkNotificationsEnabled) {
+            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);
+        }
+        
+        return false;
+    }
+    
+    //============================
+    // Wifi-related
+    //============================
+
+    public WifiLayer getWifiLayer() {
+        return mWifiLayer;
+    }
+
+    private void showAddOtherNetworkDialog() {
+        AccessPointDialog dialog = new AccessPointDialog(this, mWifiLayer);
+        dialog.setState(new AccessPointState(this));
+        dialog.setMode(AccessPointDialog.MODE_CONFIGURE);
+        dialog.setTitle(R.string.wifi_add_other_network);
+        dialog.setAutoSecurityAllowed(false);
+        showDialog(dialog);
+    }
+    
+    public void showAccessPointDialog(AccessPointState state, int mode) {
+        AccessPointDialog dialog = new AccessPointDialog(this, mWifiLayer);
+        dialog.setMode(mode);
+        dialog.setState(state);
+        showDialog(dialog);
+    }
+
+    private void showDialog(Dialog dialog) {
+        // Have only one dialog open at a time
+        if (mDialog != null) {
+            mDialog.dismiss();
+        }
+        
+        mDialog = dialog;
+        dialog.setOnDismissListener(this);
+        if (dialog != null) {
+            dialog.show();
+        }
+    }
+    
+    //============================
+    // Wifi callbacks
+    //============================
+
+    public void onError(int messageResId) {
+        Toast.makeText(this, messageResId, Toast.LENGTH_LONG).show();
+    }
+
+    public void onScanningStatusChanged(boolean started) {
+        mApCategory.setProgress(started);
+    }    
+
+    public void onAccessPointSetChanged(AccessPointState ap, boolean added) {
+
+        AccessPointPreference pref = mAps.get(ap);
+        
+        if (WifiLayer.LOGV) {
+            Log.v(TAG, "onAccessPointSetChanged with " + ap + " and "
+                    + (added ? "added" : "removed") + ", found pref " + pref);
+        }
+        
+        if (added) {
+            
+            if (pref == null) {
+                pref = new AccessPointPreference(this, ap);
+                mAps.put(ap, pref);
+            } else {
+                pref.setEnabled(true);
+            }
+            
+            mApCategory.addPreference(pref);
+            
+        } else {
+
+            mAps.remove(ap);
+            
+            if (pref != null) {
+                mApCategory.removePreference(pref);
+            }
+            
+        }
+    }
+
+    public void onAccessPointsStateChanged(boolean enabled) {
+        if (enabled) {
+            mApCategory.setEnabled(true);
+        } else {
+            mApCategory.removeAll();
+            mAps.clear();
+        }
+
+        mAddOtherNetwork.setEnabled(enabled);
+    }
+
+    public void onRetryPassword(AccessPointState ap) {
+
+        if ((mDialog != null) && mDialog.isShowing()) {
+            // If we're already showing a dialog, ignore this request
+            return;
+        }
+        
+        showAccessPointDialog(ap, AccessPointDialog.MODE_RETRY_PASSWORD);
+    }
+    
+}
diff --git a/src/com/android/settings/wifi/WifiStatus.java b/src/com/android/settings/wifi/WifiStatus.java
new file mode 100644
index 0000000..d4b6431
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiStatus.java
@@ -0,0 +1,152 @@
+/*
+ * 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.wifi;
+
+import com.android.settings.R;
+
+import android.content.Context;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
+import android.text.TextUtils;
+
+public class WifiStatus {
+
+    // e.g., "Connecting"
+    public static String sScanning;
+    public static String sConnecting;
+    public static String sAuthenticating;
+    public static String sObtainingIp;
+    public static String sConnected;
+    public static String sDisconnecting;
+    public static String sDisconnected;
+    public static String sFailed;    
+
+    // e.g., "Connecting to %1$s"
+    public static String sScanningFragment;
+    public static String sConnectingFragment;
+    public static String sAuthenticatingFragment;
+    public static String sObtainingIpFragment;
+    public static String sConnectedFragment;
+    public static String sDisconnectingFragment;
+    public static String sDisconnectedFragment;
+    public static String sFailedFragment;    
+
+    private static void fillStrings(Context context) {
+        sScanning = context.getString(R.string.status_scanning);
+        sConnecting = context.getString(R.string.status_connecting);
+        sAuthenticating = context.getString(R.string.status_authenticating);
+        sObtainingIp = context.getString(R.string.status_obtaining_ip);
+        sConnected = context.getString(R.string.status_connected);
+        sDisconnecting = context.getString(R.string.status_disconnecting);
+        sDisconnected = context.getString(R.string.status_disconnected);
+        sFailed = context.getString(R.string.status_failed);
+
+        sScanningFragment = context.getString(R.string.fragment_status_scanning);
+        sConnectingFragment = context.getString(R.string.fragment_status_connecting);
+        sAuthenticatingFragment = context.getString(R.string.fragment_status_authenticating);
+        sObtainingIpFragment = context.getString(R.string.fragment_status_obtaining_ip);
+        sConnectedFragment = context.getString(R.string.fragment_status_connected);
+        sDisconnectingFragment = context.getString(R.string.fragment_status_disconnecting);
+        sDisconnectedFragment = context.getString(R.string.fragment_status_disconnected);
+        sFailedFragment = context.getString(R.string.fragment_status_failed);
+    }
+    
+    public static String getStatus(Context context, String ssid,
+            NetworkInfo.DetailedState detailedState) {
+        
+        if (!TextUtils.isEmpty(ssid) && isLiveConnection(detailedState)) {
+            return getPrintableFragment(context, detailedState, ssid);
+        } else {
+            return getPrintable(context, detailedState);
+        }
+    }
+    
+    public static boolean isLiveConnection(NetworkInfo.DetailedState detailedState) {
+        return detailedState != NetworkInfo.DetailedState.DISCONNECTED
+                && detailedState != NetworkInfo.DetailedState.FAILED
+                && detailedState != NetworkInfo.DetailedState.IDLE
+                && detailedState != NetworkInfo.DetailedState.SCANNING;
+    }
+    
+    public static String getPrintable(Context context,
+            NetworkInfo.DetailedState detailedState) {
+        
+        if (sScanning == null) {
+            fillStrings(context);
+        }
+        
+        switch (detailedState) {
+            case AUTHENTICATING:
+                return sAuthenticating;
+            case CONNECTED:
+                return sConnected;
+            case CONNECTING:
+                return sConnecting;
+            case DISCONNECTED:
+                return sDisconnected;
+            case DISCONNECTING:
+                return sDisconnecting;
+            case FAILED:
+                return sFailed;
+            case OBTAINING_IPADDR:
+                return sObtainingIp;
+            case SCANNING:
+                return sScanning;
+            default:
+                return null;
+        }
+    }
+    
+    public static String getPrintableFragment(Context context,
+            NetworkInfo.DetailedState detailedState, String apName) {
+        
+        if (sScanningFragment == null) {
+            fillStrings(context);
+        }
+
+        String fragment = null;
+        switch (detailedState) {
+            case AUTHENTICATING:
+                fragment = sAuthenticatingFragment;
+                break;
+            case CONNECTED:
+                fragment = sConnectedFragment;
+                break;
+            case CONNECTING:
+                fragment = sConnectingFragment;
+                break;
+            case DISCONNECTED:
+                fragment = sDisconnectedFragment;
+                break;
+            case DISCONNECTING:
+                fragment = sDisconnectingFragment;
+                break;
+            case FAILED:
+                fragment = sFailedFragment;
+                break;
+            case OBTAINING_IPADDR:
+                fragment = sObtainingIpFragment;
+                break;
+            case SCANNING:
+                fragment = sScanningFragment;
+                break;
+        }
+        
+        return String.format(fragment, apName);
+    }
+    
+}