Close scan screen when pairing and remove device when unpairing.

Close the scan screen after successful pairing, and remove a
device from the list of paired devices after unpairing.

As part of the fix, BluetoothSettings was refactored into a parent
class, DeviceListPreferenceFragment, and three subclasses for each
variant type: BluetoothSettings, BluetoothFindNearby, and
DevicePickerFragment, replacing the checks against mScreenType with
custom logic in the child classes.

Bug: 3325848
Change-Id: If64fddc3ba5b4f1136451491c7d5a1139b696e47
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 22fc4bf..613b082 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -124,7 +124,7 @@
 
     /**
      * Switch to parent fragment and store the grand parent's info
-     * @param class name of the activity wrapper for the parent fragment.
+     * @param className name of the activity wrapper for the parent fragment.
      */
     private void switchToParent(String className) {
         final ComponentName cn = new ComponentName(this, className);
diff --git a/src/com/android/settings/UserLeaveHintListener.java b/src/com/android/settings/UserLeaveHintListener.java
deleted file mode 100644
index c5c2a7a..0000000
--- a/src/com/android/settings/UserLeaveHintListener.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-/**
- * Interface enabling fragments to listen to Activity#onUserLeaveHint().
- */
-public interface UserLeaveHintListener {
-    public void onUserLeaveHint();
-}
\ No newline at end of file
diff --git a/src/com/android/settings/bluetooth/BluetoothFindNearby.java b/src/com/android/settings/bluetooth/BluetoothFindNearby.java
new file mode 100644
index 0000000..f1b876e
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothFindNearby.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2011 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.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.util.Log;
+
+import com.android.settings.R;
+
+/**
+ * Fragment to scan and show the discoverable devices.
+ */
+public class BluetoothFindNearby extends DeviceListPreferenceFragment {
+
+    private static final String TAG = "BluetoothFindNearby";
+
+    void addPreferencesForActivity(Activity activity) {
+        addPreferencesFromResource(R.xml.device_picker);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mSelectedDevice != null) {
+            CachedBluetoothDevice device =
+                    mLocalManager.getCachedDeviceManager().findDevice(mSelectedDevice);
+            if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
+                // selected device was paired, so return from this screen
+                finish();
+                return;
+            }
+        }
+        mLocalManager.startScanning(true);
+    }
+
+    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
+        mLocalManager.stopScanning();
+        super.onDevicePreferenceClick(btPreference);
+    }
+
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice,
+            int bondState) {
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            // return from scan screen after successful auto-pairing
+            finish();
+        }
+    }
+
+    void onBluetoothStateChanged(int bluetoothState) {
+        super.onBluetoothStateChanged(bluetoothState);
+
+        if (bluetoothState == BluetoothAdapter.STATE_ON) {
+                mLocalManager.startScanning(false);
+        }
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index 54f9fb1..2893ce3 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2011 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.
@@ -16,166 +16,41 @@
 
 package com.android.settings.bluetooth;
 
-import com.android.settings.ProgressCategory;
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.UserLeaveHintListener;
-
 import android.app.Activity;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothDevicePicker;
-import android.bluetooth.BluetoothUuid;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.ParcelUuid;
 import android.preference.CheckBoxPreference;
-import android.preference.Preference;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceScreen;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 
-import java.util.List;
-import java.util.WeakHashMap;
+import com.android.settings.R;
 
 /**
  * BluetoothSettings is the Settings screen for Bluetooth configuration and
  * connection management.
  */
-public class BluetoothSettings extends SettingsPreferenceFragment
-        implements LocalBluetoothManager.Callback, UserLeaveHintListener, View.OnClickListener {
+public class BluetoothSettings extends DeviceListPreferenceFragment
+        implements LocalBluetoothManager.Callback, View.OnClickListener {
 
     private static final String TAG = "BluetoothSettings";
 
     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 static final int SCREEN_TYPE_SETTINGS = 0;
-    private static final int SCREEN_TYPE_DEVICEPICKER = 1;
-    private static final int SCREEN_TYPE_SCAN = 2;
-
-    public static final String ACTION = "bluetooth_action";
-    public static final String ACTION_LAUNCH_SCAN_MODE =
-            "com.android.settings.bluetooth.action.LAUNCH_SCAN_MODE";
-
-    /*package*/ int mScreenType;
-    private int mFilterType;
-    private boolean mNeedAuth;
-    private String mLaunchPackage;
-    private String mLaunchClass;
-
-    /*package*/ BluetoothDevice mSelectedDevice= null;
-
-    /*package*/ LocalBluetoothManager mLocalManager;
 
     private BluetoothEnabler mEnabler;
     private BluetoothDiscoverableEnabler mDiscoverableEnabler;
-
     private BluetoothNamePreference mNamePreference;
 
-    private PreferenceCategory mDeviceList;
+    void addPreferencesForActivity(Activity activity) {
+        addPreferencesFromResource(R.xml.bluetooth_settings);
 
-    private WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
-            new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>();
+        mEnabler = new BluetoothEnabler(activity,
+                (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX));
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
-                onBluetoothStateChanged(mLocalManager.getBluetoothState());
-            } else if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
-                // TODO: If this is a scanning screen, maybe return on successful pairing
+        mDiscoverableEnabler = new BluetoothDiscoverableEnabler(activity,
+                (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE));
 
-                int bondState = intent
-                        .getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
-                if (bondState == BluetoothDevice.BOND_BONDED) {
-                    BluetoothDevice device = intent
-                            .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                    if (device.equals(mSelectedDevice)) {
-                        if (mScreenType == SCREEN_TYPE_DEVICEPICKER) {
-                            sendDevicePickedIntent(device);
-                            finish();
-                        } else if (mScreenType == SCREEN_TYPE_SCAN) {
-                            finish();
-                        }
-                    }
-                }
-            }
-        }
-    };
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        // We delay calling super.onActivityCreated(). See WifiSettings.java for more info.
-
-        final Activity activity = getActivity();
-        mLocalManager = LocalBluetoothManager.getInstance(activity);
-        if (mLocalManager == null) {
-            finish();
-        }
-
-        // Note:
-        // If an application wishes to show the BT device list, it can send an
-        // intent to Settings application with the following extra data:
-        // -DEVICE_PICKER_FILTER_TYPE: the type of BT devices to show.
-        // -DEVICE_PICKER_LAUNCH_PACKAGE: the package which the application belongs to.
-        // -DEVICE_PICKER_LAUNCH_CLASS: the class which will receive user's selected
-        // result from the BT list.
-        // -DEVICE_PICKER_NEED_AUTH: to show if bonding procedure needed.
-
-        mFilterType = BluetoothDevicePicker.FILTER_TYPE_ALL;
-        final Intent intent = activity.getIntent();
-
-        // This additional argument comes from PreferenceScreen (See TetherSettings.java).
-        Bundle args = getArguments();
-        String action = args != null ? args.getString(ACTION) : null;
-        if (TextUtils.isEmpty(action)) {
-            action = intent.getAction();
-        }
-
-        if (getPreferenceScreen() != null) getPreferenceScreen().removeAll();
-
-        if (action.equals(BluetoothDevicePicker.ACTION_LAUNCH)) {
-            mScreenType = SCREEN_TYPE_DEVICEPICKER;
-            mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
-            mFilterType = intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
-                    BluetoothDevicePicker.FILTER_TYPE_ALL);
-            mLaunchPackage = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE);
-            mLaunchClass = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS);
-
-            activity.setTitle(activity.getString(R.string.device_picker));
-            addPreferencesFromResource(R.xml.device_picker);
-        } else if (action.equals(ACTION_LAUNCH_SCAN_MODE)) {
-            mScreenType = SCREEN_TYPE_SCAN;
-
-            addPreferencesFromResource(R.xml.device_picker);
-        } else {
-            addPreferencesFromResource(R.xml.bluetooth_settings);
-
-            mEnabler = new BluetoothEnabler(
-                    activity,
-                    (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX));
-
-            mDiscoverableEnabler = new BluetoothDiscoverableEnabler(
-                    activity,
-                    (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE));
-
-            mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME);
-
-        }
-
-        mDeviceList = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST);
-
-        super.onActivityCreated(savedInstanceState);
+        mNamePreference = (BluetoothNamePreference) findPreference(KEY_BT_NAME);
     }
 
     @Override
@@ -184,251 +59,53 @@
 
         // Repopulate (which isn't too bad since it's cached in the settings
         // bluetooth manager)
-        mDevicePreferenceMap.clear();
-        mDeviceList.removeAll();
-        if (mScreenType != SCREEN_TYPE_SCAN) {
-            addDevices();
-        }
+        addDevices();
 
-        if (mScreenType == SCREEN_TYPE_SETTINGS) {
-            mEnabler.resume();
-            mDiscoverableEnabler.resume();
-            mNamePreference.resume();
-        }
-
-        mLocalManager.registerCallback(this);
-
-        updateProgressUi(mLocalManager.getBluetoothAdapter().isDiscovering());
-
-        if (mScreenType != SCREEN_TYPE_SETTINGS) {
-            mLocalManager.startScanning(true);
-        }
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
-        intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        getActivity().registerReceiver(mReceiver, intentFilter);
-        mLocalManager.setForegroundActivity(getActivity());
+        mEnabler.resume();
+        mDiscoverableEnabler.resume();
+        mNamePreference.resume();
     }
 
     @Override
     public void onPause() {
         super.onPause();
-        mLocalManager.setForegroundActivity(null);
-        mDevicePreferenceMap.clear();
-        mDeviceList.removeAll();
-        getActivity().unregisterReceiver(mReceiver);
 
-        mLocalManager.unregisterCallback(this);
-        if (mScreenType == SCREEN_TYPE_SETTINGS) {
-            mNamePreference.pause();
-            mDiscoverableEnabler.pause();
-            mEnabler.pause();
-        }
+        mNamePreference.pause();
+        mDiscoverableEnabler.pause();
+        mEnabler.pause();
     }
 
-    public void onUserLeaveHint() {
-        mLocalManager.stopScanning();
-    }
-
-    private void addDevices() {
-        List<CachedBluetoothDevice> cachedDevices =
-                mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
-        for (CachedBluetoothDevice cachedDevice : cachedDevices) {
-            onDeviceAdded(cachedDevice);
-        }
-    }
-
-    public void onClick(View v) {
-        // User clicked on advanced options icon for a device in the list
-        if (v.getTag() instanceof CachedBluetoothDevice) {
-            CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
-            device.onClickedAdvancedOptions(this);
-        }
-    }
-
-    @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;
-            CachedBluetoothDevice device = btPreference.getCachedDevice();
-            mSelectedDevice = device.getDevice();
-
-            if (mScreenType == SCREEN_TYPE_SETTINGS || mScreenType == SCREEN_TYPE_SCAN) {
-                btPreference.getCachedDevice().onClicked();
-            } else if (mScreenType == SCREEN_TYPE_DEVICEPICKER) {
-                mLocalManager.stopScanning();
-                mLocalManager.persistSelectedDeviceInPicker(mSelectedDevice.getAddress());
-                if ((device.getBondState() == BluetoothDevice.BOND_BONDED) ||
-                        (mNeedAuth == false)) {
-                    sendDevicePickedIntent(mSelectedDevice);
-                    finish();
-                } else {
-                    btPreference.getCachedDevice().onClicked();
-                }
-            } else {
-                Log.e(TAG, "onPreferenceTreeClick has invalid mScreenType: "
-                        + mScreenType);
-            }
-            return true;
-        }
-
-        return super.onPreferenceTreeClick(preferenceScreen, preference);
-    }
-
-    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
-
-        if (mDevicePreferenceMap.get(cachedDevice) != null) {
-            Log.e(TAG, "Got onDeviceAdded, but cachedDevice already exists");
-            return;
-        }
-
-        if (mScreenType != SCREEN_TYPE_SETTINGS
-                || cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
-            if (addDevicePreference(cachedDevice)) {
-                createDevicePreference(cachedDevice);
-            }
-        }
-     }
-
     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice,
             int bondState) {
-        // add to "Paired devices" list after remote-initiated pairing
-        if (mDevicePreferenceMap.get(cachedDevice) == null &&
-                mScreenType == SCREEN_TYPE_SETTINGS &&
-                bondState == BluetoothDevice.BOND_BONDED) {
-            if (addDevicePreference(cachedDevice)) {
-                createDevicePreference(cachedDevice);
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            // add to "Paired devices" list after remote-initiated pairing
+            if (mDevicePreferenceMap.get(cachedDevice) == null) {
+                if (addDevicePreference(cachedDevice)) {
+                    createDevicePreference(cachedDevice);
+                }
             }
+        } else if (bondState == BluetoothDevice.BOND_NONE) {
+            // remove unpaired device from paired devices list
+            onDeviceDeleted(cachedDevice);
         }
     }
 
-    private boolean addDevicePreference(CachedBluetoothDevice cachedDevice) {
-        ParcelUuid[] uuids = cachedDevice.getDevice().getUuids();
-        BluetoothClass bluetoothClass = cachedDevice.getDevice().getBluetoothClass();
-
-        switch(mFilterType) {
-        case BluetoothDevicePicker.FILTER_TYPE_TRANSFER:
-            if (uuids != null) {
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.OPP_PROFILE_UUIDS))  return true;
-            }
-            if (bluetoothClass != null
-                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_OPP)) {
-                return true;
-            }
-            break;
-        case BluetoothDevicePicker.FILTER_TYPE_AUDIO:
-            if (uuids != null) {
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.A2DP_SINK_PROFILE_UUIDS))  return true;
-
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.HEADSET_PROFILE_UUIDS))  return true;
-            } else if (bluetoothClass != null) {
-                if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) return true;
-
-                if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) return true;
-            }
-            break;
-        case BluetoothDevicePicker.FILTER_TYPE_PANU:
-            if (uuids != null) {
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.PANU_PROFILE_UUIDS))  return true;
-
-            }
-            if (bluetoothClass != null
-                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_PANU)) {
-                return true;
-            }
-            break;
-        case BluetoothDevicePicker.FILTER_TYPE_NAP:
-            if (uuids != null) {
-                if (BluetoothUuid.containsAnyUuid(uuids,
-                        LocalBluetoothProfileManager.NAP_PROFILE_UUIDS))  return true;
-            }
-            if (bluetoothClass != null
-                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_NAP)) {
-                return true;
-            }
-            break;
-        default:
-            return true;
-        }
-        return false;
-    }
-
-    private void createDevicePreference(CachedBluetoothDevice cachedDevice) {
-        BluetoothDevicePreference preference = new BluetoothDevicePreference(
-                getActivity(), cachedDevice);
-
-        if (mScreenType == SCREEN_TYPE_SETTINGS) {
-            preference.setOnSettingsClickListener(this);
-        }
-        mDeviceList.addPreference(preference);
-        mDevicePreferenceMap.put(cachedDevice, preference);
-    }
-
-    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
-        BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
-        if (preference != null) {
-            mDeviceList.removePreference(preference);
+    /**
+     * Additional check to only add paired devices to list.
+     */
+    boolean addDevicePreference(CachedBluetoothDevice cachedDevice) {
+        if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
+            return super.addDevicePreference(cachedDevice);
+        } else {
+            return false;
         }
     }
 
-    public void onScanningStateChanged(boolean started) {
-        updateProgressUi(started);
-    }
-
-    private void updateProgressUi(boolean start) {
-        if (mDeviceList instanceof ProgressCategory) {
-            ((ProgressCategory) mDeviceList).setProgress(start);
-        }
-    }
-
-    /*package*/ void onBluetoothStateChanged(int bluetoothState) {
-        // When bluetooth is enabled (and we are in the activity, which we are),
-        // we should start a scan
-        if (bluetoothState == BluetoothAdapter.STATE_ON) {
-            if (mScreenType != SCREEN_TYPE_SETTINGS) {
-                mLocalManager.startScanning(false);
-            }
-        } else if (bluetoothState == BluetoothAdapter.STATE_OFF) {
-            updateProgressUi(false);
-        }
-    }
-
-    /*package*/ void sendDevicePickedIntent(BluetoothDevice device) {
-        Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        if (mScreenType == SCREEN_TYPE_DEVICEPICKER &&
-                mLaunchPackage != null && mLaunchClass != null) {
-            intent.setClassName(mLaunchPackage, mLaunchClass);
-        }
-        getActivity().sendBroadcast(intent);
-    }
-
-    public static class FindNearby extends BluetoothSettings {
-
-        public FindNearby() {
-        }
-
-        @Override
-        public void onActivityCreated(Bundle savedInstanceState) {
-            Bundle args = super.getArguments();
-            if (args == null) {
-                args = new Bundle();
-                setArguments(args);
-            }
-            args.putString(ACTION, ACTION_LAUNCH_SCAN_MODE);
-            super.onActivityCreated(savedInstanceState);
-        }
+    /**
+     * Add a listener, which enables the advanced settings icon.
+     * @param preference the newly added preference
+     */
+    void initDevicePreference(BluetoothDevicePreference preference) {
+        preference.setOnSettingsClickListener(this);
     }
 }
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
new file mode 100644
index 0000000..46fff6e
--- /dev/null
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2011 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.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevicePicker;
+import android.bluetooth.BluetoothUuid;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.view.View;
+
+import com.android.settings.ProgressCategory;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.List;
+import java.util.WeakHashMap;
+
+/**
+ * Parent class for settings fragments that contain a list of Bluetooth
+ * devices.
+ *
+ * @see BluetoothSettings
+ * @see DevicePickerFragment
+ * @see BluetoothFindNearby
+ */
+public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFragment
+        implements LocalBluetoothManager.Callback, View.OnClickListener {
+
+    private static final String TAG = "DeviceListPreferenceFragment";
+
+    static final String KEY_BT_DEVICE_LIST = "bt_device_list";
+    static final String KEY_BT_SCAN = "bt_scan";
+
+    int mFilterType;
+
+    BluetoothDevice mSelectedDevice = null;
+
+    LocalBluetoothManager mLocalManager;
+
+    private PreferenceCategory mDeviceList;
+
+    WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
+            new WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference>();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+                onBluetoothStateChanged(mLocalManager.getBluetoothState());
+            }
+        }
+    };
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        // We delay calling super.onActivityCreated(). See WifiSettings.java for more info.
+
+        final Activity activity = getActivity();
+        mLocalManager = LocalBluetoothManager.getInstance(activity);
+        if (mLocalManager == null) {
+            finish();
+        }
+
+        mFilterType = BluetoothDevicePicker.FILTER_TYPE_ALL;
+
+        if (getPreferenceScreen() != null) getPreferenceScreen().removeAll();
+
+        addPreferencesForActivity(activity);
+
+        mDeviceList = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST);
+
+        super.onActivityCreated(savedInstanceState);
+    }
+
+    /** Add preferences from the subclass. */
+    abstract void addPreferencesForActivity(Activity activity);
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        mLocalManager.registerCallback(this);
+
+        updateProgressUi(mLocalManager.getBluetoothAdapter().isDiscovering());
+
+        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        getActivity().registerReceiver(mReceiver, intentFilter);
+        mLocalManager.setForegroundActivity(getActivity());
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mLocalManager.stopScanning();
+        mLocalManager.setForegroundActivity(null);
+        mDevicePreferenceMap.clear();
+        mDeviceList.removeAll();
+        getActivity().unregisterReceiver(mReceiver);
+
+        mLocalManager.unregisterCallback(this);
+    }
+
+    void addDevices() {
+        List<CachedBluetoothDevice> cachedDevices =
+                mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
+        for (CachedBluetoothDevice cachedDevice : cachedDevices) {
+            onDeviceAdded(cachedDevice);
+        }
+    }
+
+    public void onClick(View v) {
+        // User clicked on advanced options icon for a device in the list
+        if (v.getTag() instanceof CachedBluetoothDevice) {
+            CachedBluetoothDevice device = (CachedBluetoothDevice) v.getTag();
+            device.onClickedAdvancedOptions(this);
+        }
+    }
+
+    @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;
+            CachedBluetoothDevice device = btPreference.getCachedDevice();
+            mSelectedDevice = device.getDevice();
+            onDevicePreferenceClick(btPreference);
+            return true;
+        }
+
+        return super.onPreferenceTreeClick(preferenceScreen, preference);
+    }
+
+    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
+        btPreference.getCachedDevice().onClicked();
+    }
+
+    public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
+        if (mDevicePreferenceMap.get(cachedDevice) != null) {
+            Log.e(TAG, "Got onDeviceAdded, but cachedDevice already exists");
+            return;
+        }
+
+        if (addDevicePreference(cachedDevice)) {
+            createDevicePreference(cachedDevice);
+        }
+     }
+
+    /**
+     * Determine whether to add the new device to the list.
+     * @param cachedDevice the newly discovered device
+     * @return true if the device should be added; false otherwise
+     */
+    boolean addDevicePreference(CachedBluetoothDevice cachedDevice) {
+        ParcelUuid[] uuids = cachedDevice.getDevice().getUuids();
+        BluetoothClass bluetoothClass = cachedDevice.getDevice().getBluetoothClass();
+
+        switch(mFilterType) {
+        case BluetoothDevicePicker.FILTER_TYPE_TRANSFER:
+            if (uuids != null) {
+                if (BluetoothUuid.containsAnyUuid(uuids,
+                        LocalBluetoothProfileManager.OPP_PROFILE_UUIDS))  return true;
+            }
+            if (bluetoothClass != null
+                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_OPP)) {
+                return true;
+            }
+            break;
+        case BluetoothDevicePicker.FILTER_TYPE_AUDIO:
+            if (uuids != null) {
+                if (BluetoothUuid.containsAnyUuid(uuids,
+                        LocalBluetoothProfileManager.A2DP_SINK_PROFILE_UUIDS))  return true;
+
+                if (BluetoothUuid.containsAnyUuid(uuids,
+                        LocalBluetoothProfileManager.HEADSET_PROFILE_UUIDS))  return true;
+            } else if (bluetoothClass != null) {
+                if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) return true;
+
+                if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) return true;
+            }
+            break;
+        case BluetoothDevicePicker.FILTER_TYPE_PANU:
+            if (uuids != null) {
+                if (BluetoothUuid.containsAnyUuid(uuids,
+                        LocalBluetoothProfileManager.PANU_PROFILE_UUIDS))  return true;
+
+            }
+            if (bluetoothClass != null
+                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_PANU)) {
+                return true;
+            }
+            break;
+        case BluetoothDevicePicker.FILTER_TYPE_NAP:
+            if (uuids != null) {
+                if (BluetoothUuid.containsAnyUuid(uuids,
+                        LocalBluetoothProfileManager.NAP_PROFILE_UUIDS))  return true;
+            }
+            if (bluetoothClass != null
+                   && bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_NAP)) {
+                return true;
+            }
+            break;
+        default:
+            return true;
+        }
+        return false;
+    }
+
+    void createDevicePreference(CachedBluetoothDevice cachedDevice) {
+        BluetoothDevicePreference preference = new BluetoothDevicePreference(
+                getActivity(), cachedDevice);
+
+        initDevicePreference(preference);
+        mDeviceList.addPreference(preference);
+        mDevicePreferenceMap.put(cachedDevice, preference);
+    }
+
+    /**
+     * Overridden in {@link BluetoothSettings} to add a listener.
+     * @param preference the newly added preference
+     */
+    void initDevicePreference(BluetoothDevicePreference preference) { }
+
+    public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
+        BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
+        if (preference != null) {
+            mDeviceList.removePreference(preference);
+        }
+    }
+
+    public void onScanningStateChanged(boolean started) {
+        updateProgressUi(started);
+    }
+
+    private void updateProgressUi(boolean start) {
+        if (mDeviceList instanceof ProgressCategory) {
+            ((ProgressCategory) mDeviceList).setProgress(start);
+        }
+    }
+
+    void onBluetoothStateChanged(int bluetoothState) {
+        if (bluetoothState == BluetoothAdapter.STATE_OFF) {
+            updateProgressUi(false);
+        }
+    }
+
+    void sendDevicePickedIntent(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        getActivity().sendBroadcast(intent);
+    }
+}
diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java
new file mode 100644
index 0000000..d3e3d69
--- /dev/null
+++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2011 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.Activity;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevicePicker;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.settings.R;
+
+/**
+ * BluetoothSettings is the Settings screen for Bluetooth configuration and
+ * connection management.
+ */
+public class DevicePickerFragment extends DeviceListPreferenceFragment {
+
+    private static final String TAG = "BluetoothDevicePicker";
+
+    private boolean mNeedAuth;
+    private String mLaunchPackage;
+    private String mLaunchClass;
+
+    void addPreferencesForActivity(Activity activity) {
+        Intent intent = activity.getIntent();
+        mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false);
+        mFilterType = intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE,
+                BluetoothDevicePicker.FILTER_TYPE_ALL);
+        mLaunchPackage = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE);
+        mLaunchClass = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS);
+
+        activity.setTitle(activity.getString(R.string.device_picker));
+        addPreferencesFromResource(R.xml.device_picker);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        addDevices();
+        mLocalManager.startScanning(true);
+    }
+
+    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
+        mLocalManager.stopScanning();
+        mLocalManager.persistSelectedDeviceInPicker(mSelectedDevice.getAddress());
+        if ((btPreference.getCachedDevice().getBondState() ==
+                BluetoothDevice.BOND_BONDED) || !mNeedAuth) {
+            sendDevicePickedIntent(mSelectedDevice);
+            finish();
+        } else {
+            super.onDevicePreferenceClick(btPreference);
+        }
+    }
+
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice,
+            int bondState) {
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            BluetoothDevice device = cachedDevice.getDevice();
+            if (device.equals(mSelectedDevice)) {
+                sendDevicePickedIntent(device);
+                finish();
+            }
+        }
+    }
+
+    void onBluetoothStateChanged(int bluetoothState) {
+        super.onBluetoothStateChanged(bluetoothState);
+
+        if (bluetoothState == BluetoothAdapter.STATE_ON) {
+                mLocalManager.startScanning(false);
+        }
+    }
+
+    void sendDevicePickedIntent(BluetoothDevice device) {
+        Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        if (mLaunchPackage != null && mLaunchClass != null) {
+            intent.setClassName(mLaunchPackage, mLaunchClass);
+        }
+        getActivity().sendBroadcast(intent);
+    }
+}