Double list in Bluetooth Settings

Paired devices are listed first (from cache), followed by unpaired ones.
A scan is only started on user request or when there is no paired device
(should it be when there is no paired *connected* device?).

Wrench icon only displayed for paired devices.

Wrench click listener no longer uses mDeviceSettings which is unreliable
with ListView view recycling.

Fixed blinking ProgressCategory when the category was first in the list.

Change-Id: Ie749883426c12bd354da64733bd04b00304bc1f5
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 137b171..993eb30 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -936,8 +936,8 @@
 
     <!-- Bluetooth settings: The title of the preference (list item) that initiates a scan for devices -->
     <string name="bluetooth_preference_scan_title">Scan for devices</string>
-    <!-- Bluetooth settings: The title of the action button that finds nearby devices [CHAR LIMIT=20] -->
-    <string name="bluetooth_preference_find_nearby_title">Scan</string>
+    <!-- Bluetooth settings: The title of the action button that initiates a scan for nearby devices [CHAR LIMIT=20] -->
+    <string name="bluetooth_scan_nearby_devices">Scan</string>
     <!-- Bluetooth settings: The sub heading for device settings. [CHAR LIMIT=30] -->
     <string name="bluetooth_preference_device_settings">Device settings</string>
     <!-- Bluetooth settings: The sub heading for paired devices. [CHAR LIMIT=30] -->
diff --git a/src/com/android/settings/ProgressCategory.java b/src/com/android/settings/ProgressCategory.java
index c5b68b6..bedcc98 100644
--- a/src/com/android/settings/ProgressCategory.java
+++ b/src/com/android/settings/ProgressCategory.java
@@ -23,7 +23,6 @@
 public class ProgressCategory extends ProgressCategoryBase {
 
     private boolean mProgress = false;
-    private View oldView = null;
 
     public ProgressCategory(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -39,13 +38,6 @@
         final int visibility = mProgress ? View.VISIBLE : View.INVISIBLE;
         textView.setVisibility(visibility);
         progressBar.setVisibility(visibility);
-
-        if (oldView != null) {
-            oldView.findViewById(R.id.scanning_progress).setVisibility(View.GONE);
-            oldView.findViewById(R.id.scanning_text).setVisibility(View.GONE);
-            oldView.setVisibility(View.GONE);
-        }
-        oldView = view;
     }
 
     @Override
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java
index 00e342c..e4f11a2 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java
@@ -42,6 +42,9 @@
     /** Bonded devices only filter (referenced directly). */
     static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter();
 
+    /** Unbonded devices only filter (referenced directly). */
+    static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter();
+
     /** Table of singleton filter objects. */
     private static final Filter[] FILTERS = {
             ALL_FILTER,             // FILTER_TYPE_ALL
@@ -85,6 +88,13 @@
         }
     }
 
+    /** Filter that matches only unbonded devices. */
+    private static final class UnbondedDeviceFilter implements Filter {
+        public boolean matches(BluetoothDevice device) {
+            return device.getBondState() != BluetoothDevice.BOND_BONDED;
+        }
+    }
+
     /** Parent class of filters based on UUID and/or Bluetooth class. */
     private abstract static class ClassUuidFilter implements Filter {
         abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass);
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index 391c941..06c708b 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -49,8 +49,6 @@
 
     private final CachedBluetoothDevice mCachedDevice;
 
-    private ImageView mDeviceSettings;
-
     private OnClickListener mOnSettingsClickListener;
 
     private AlertDialog mDisconnectDialog;
@@ -121,13 +119,13 @@
         btClass.setImageResource(getBtClassDrawable());
         btClass.setAlpha(isEnabled() ? 255 : sDimAlpha);
         btClass.setVisibility(View.VISIBLE);
-        mDeviceSettings = (ImageView) view.findViewById(R.id.deviceDetails);
+        ImageView deviceDetails = (ImageView) view.findViewById(R.id.deviceDetails);
         if (mOnSettingsClickListener != null) {
-            mDeviceSettings.setOnClickListener(this);
-            mDeviceSettings.setTag(mCachedDevice);
-            mDeviceSettings.setAlpha(isEnabled() ? 255 : sDimAlpha);
+            deviceDetails.setOnClickListener(this);
+            deviceDetails.setTag(mCachedDevice);
+            deviceDetails.setAlpha(isEnabled() ? 255 : sDimAlpha);
         } else { // Hide the settings icon and divider
-            mDeviceSettings.setVisibility(View.GONE);
+            deviceDetails.setVisibility(View.GONE);
             View divider = view.findViewById(R.id.divider);
             if (divider != null) {
                 divider.setVisibility(View.GONE);
@@ -152,13 +150,13 @@
     }
 
     public void onClick(View v) {
-        if (v == mDeviceSettings) {
-            if (mOnSettingsClickListener != null) {
-                mOnSettingsClickListener.onClick(v);
-            }
+        // Should never be null by construction
+        if (mOnSettingsClickListener != null) {
+            mOnSettingsClickListener.onClick(v);
         }
     }
 
+    @Override
     public boolean equals(Object o) {
         if ((o == null) || !(o instanceof BluetoothDevicePreference)) {
             return false;
@@ -167,6 +165,7 @@
                 ((BluetoothDevicePreference) o).mCachedDevice);
     }
 
+    @Override
     public int hashCode() {
         return mCachedDevice.hashCode();
     }
@@ -174,8 +173,8 @@
     @Override
     public int compareTo(Preference another) {
         if (!(another instanceof BluetoothDevicePreference)) {
-            // Put other preference types above us
-            return 1;
+            // Rely on default sort
+            return super.compareTo(another);
         }
 
         return mCachedDevice
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index f20ad78..76bf623 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -22,6 +22,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.preference.Preference;
 import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
 import android.util.Log;
 import android.view.Gravity;
@@ -31,6 +32,7 @@
 import android.view.View;
 import android.widget.Switch;
 
+import com.android.settings.ProgressCategory;
 import com.android.settings.R;
 
 /**
@@ -45,10 +47,8 @@
 
     private BluetoothEnabler mBluetoothEnabler;
 
-    /** Initialize the filter to show bonded devices only. */
-    //public BluetoothSettings() {
-    //    super(BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
-    //}
+    private PreferenceGroup mFoundDevicesCategory;
+    private boolean mFoundDevicesCategoryIsPresent;
 
     @Override
     void addPreferencesForActivity() {
@@ -101,9 +101,9 @@
     @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         boolean bluetoothIsEnabled = mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON;
-        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.bluetooth_preference_find_nearby_title)
+        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.bluetooth_scan_nearby_devices)
                 //.setIcon(R.drawable.ic_menu_scan_network)
-                .setEnabled(bluetoothIsEnabled)
+                .setEnabled(bluetoothIsEnabled && !mLocalAdapter.isDiscovering())
                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
         menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.bluetooth_menu_advanced)
                 //.setIcon(android.R.drawable.ic_menu_manage)
@@ -113,13 +113,9 @@
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
-                // TODO
-//                if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
-//                    onAddNetworkPressed();
-//                }
             case MENU_ID_SCAN:
                 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
-                    mLocalAdapter.startScanning(true);
+                    startScanning();
                 }
                 return true;
             case MENU_ID_ADVANCED:
@@ -137,24 +133,12 @@
         return super.onOptionsItemSelected(item);
     }
 
-    private final View.OnClickListener mListener = new View.OnClickListener() {
-        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();
-
-                Preference pref = new Preference(getActivity());
-                pref.setTitle(device.getName());
-                pref.setFragment(DeviceProfilesSettings.class.getName());
-                pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE,
-                        device.getDevice());
-                ((PreferenceActivity) getActivity()).onPreferenceStartFragment(
-                        BluetoothSettings.this, pref);
-            } else {
-                Log.w(TAG, "onClick() called for other View: " + v);
-            }
+    private void startScanning() {
+        if (!mFoundDevicesCategoryIsPresent) {
+            getPreferenceScreen().addPreference(mFoundDevicesCategory);
         }
-    };
+        mLocalAdapter.startScanning(true);
+    }
 
     @Override
     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
@@ -162,12 +146,6 @@
         super.onDevicePreferenceClick(btPreference);
     }
 
-    @Override
-    public void onBluetoothStateChanged(int bluetoothState) {
-        super.onBluetoothStateChanged(bluetoothState);
-        updateContent(bluetoothState);
-    }
-
     private void updateContent(int bluetoothState) {
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
         getActivity().invalidateOptionsMenu();
@@ -176,9 +154,34 @@
         switch (bluetoothState) {
             case BluetoothAdapter.STATE_ON:
                 preferenceScreen.removeAll();
-                // Repopulate (which isn't too bad since it's cached in the settings bluetooth manager)
-                addDevices();
-                mLocalAdapter.startScanning(false);
+
+                // Add bonded devices from cache first
+                setFilter(BluetoothDeviceFilter.BONDED_DEVICE_FILTER);
+                setDeviceListGroup(preferenceScreen);
+                preferenceScreen.setOrderingAsAdded(true);
+
+                addCachedDevices();
+                int numberOfPairedDevices = preferenceScreen.getPreferenceCount();
+
+                // Found devices category
+                mFoundDevicesCategory = new ProgressCategory(getActivity(), null);
+                mFoundDevicesCategory.setTitle(R.string.bluetooth_preference_found_devices);
+                preferenceScreen.addPreference(mFoundDevicesCategory);
+                mFoundDevicesCategoryIsPresent = true;
+
+                // Unbonded found devices from cache
+                setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
+                setDeviceListGroup(mFoundDevicesCategory);
+                addCachedDevices();
+
+                int numberOfUnpairedDevices = mFoundDevicesCategory.getPreferenceCount();
+                if (numberOfUnpairedDevices == 0) {
+                    preferenceScreen.removePreference(mFoundDevicesCategory);
+                    mFoundDevicesCategoryIsPresent = false;
+                }
+
+                if (numberOfPairedDevices == 0) startScanning();
+
                 return;
 
             case BluetoothAdapter.STATE_TURNING_OFF:
@@ -197,31 +200,63 @@
                 break;
         }
 
+        setDeviceListGroup(preferenceScreen);
         removeAllDevices();
+
         // TODO: from xml, add top padding. Same as in wifi
         Preference emptyListPreference = new Preference(getActivity());
         emptyListPreference.setTitle(messageId);
         preferenceScreen.addPreference(emptyListPreference);
     }
 
-    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-        if (bondState == BluetoothDevice.BOND_BONDED) {
-            // add to "Paired devices" list after remote-initiated pairing
-            if (mDevicePreferenceMap.get(cachedDevice) == null) {
-                createDevicePreference(cachedDevice);
-            }
-        } else if (bondState == BluetoothDevice.BOND_NONE) {
-            // remove unpaired device from paired devices list
-            onDeviceDeleted(cachedDevice);
-        }
+    @Override
+    public void onBluetoothStateChanged(int bluetoothState) {
+        super.onBluetoothStateChanged(bluetoothState);
+        updateContent(bluetoothState);
     }
 
+    @Override
+    public void onScanningStateChanged(boolean started) {
+        super.onScanningStateChanged(started);
+        // Update 'Scan' option enabled state
+        getActivity().invalidateOptionsMenu();
+    }
+
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        setDeviceListGroup(getPreferenceScreen());
+        removeAllDevices();
+        updateContent(mLocalAdapter.getBluetoothState());
+    }
+
+    private final View.OnClickListener mDeviceProfilesListener = new View.OnClickListener() {
+        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();
+
+                Preference pref = new Preference(getActivity());
+                pref.setTitle(device.getName());
+                pref.setFragment(DeviceProfilesSettings.class.getName());
+                pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE,
+                        device.getDevice());
+                ((PreferenceActivity) getActivity()).onPreferenceStartFragment(
+                        BluetoothSettings.this, pref);
+            } else {
+                Log.w(TAG, "onClick() called for other View: " + v); // TODO remove
+            }
+        }
+    };
+
     /**
      * Add a listener, which enables the advanced settings icon.
      * @param preference the newly added preference
      */
     @Override
     void initDevicePreference(BluetoothDevicePreference preference) {
-        preference.setOnSettingsClickListener(mListener);
+        CachedBluetoothDevice cachedDevice = preference.getCachedDevice();
+        if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
+            // Only paired device have an associated advanced settings screen
+            preference.setOnSettingsClickListener(mDeviceProfilesListener);
+        }
     }
 }
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
index 409edb9..9783fd7 100644
--- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
@@ -62,7 +62,7 @@
         mFilter = BluetoothDeviceFilter.ALL_FILTER;
     }
 
-    DeviceListPreferenceFragment(BluetoothDeviceFilter.Filter filter) {
+    final void setFilter(BluetoothDeviceFilter.Filter filter) {
         mFilter = filter;
     }
 
@@ -84,14 +84,10 @@
         addPreferencesForActivity();
 
         mDeviceListGroup = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST);
-        if (mDeviceListGroup == null) {
-            // If null, device preferences are added directly to the root of the preference screen
-            mDeviceListGroup = getPreferenceScreen();
-            mDeviceListGroup.setOrderingAsAdded(false);
-        }
-        if (mDeviceListGroup == null) {
-            Log.e(TAG, "Could not find device list preference object!");
-        }
+    }
+
+    void setDeviceListGroup(PreferenceGroup preferenceGroup) {
+        mDeviceListGroup = preferenceGroup;
     }
 
     /** Add preferences from the subclass. */
@@ -121,7 +117,7 @@
         mDeviceListGroup.removeAll();
     }
 
-    void addDevices() {
+    void addCachedDevices() {
         Collection<CachedBluetoothDevice> cachedDevices =
                 mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
         for (CachedBluetoothDevice cachedDevice : cachedDevices) {
@@ -159,7 +155,7 @@
             return;
         }
 
-        // No update while list shows state message
+        // Prevent updates while the list shows one of the state messages
         if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;
 
         if (mFilter.matches(cachedDevice.getDevice())) {
@@ -199,7 +195,6 @@
         if (mDeviceListGroup instanceof ProgressCategory) {
             ((ProgressCategory) mDeviceListGroup).setProgress(start);
         }
-        // else TODO Add a spinner at the end of the list to show in progress state
     }
 
     public void onBluetoothStateChanged(int bluetoothState) {
diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java
index 3aeb7e2..8b32941 100644
--- a/src/com/android/settings/bluetooth/DevicePickerFragment.java
+++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java
@@ -55,7 +55,7 @@
     @Override
     public void onResume() {
         super.onResume();
-        addDevices();
+        addCachedDevices();
         mLocalAdapter.startScanning(true);
     }