Settings: use foreground receiver for BT connect am: 2dbe436a43 am: 9a85a9ca1e
am: 9323a3454f

Change-Id: I393ac715b1e6eb23ca3c734b3bb583b5b49156d4
diff --git a/Settings/src/com/android/tv/settings/MainFragment.java b/Settings/src/com/android/tv/settings/MainFragment.java
index b196997..ed2ff2a 100644
--- a/Settings/src/com/android/tv/settings/MainFragment.java
+++ b/Settings/src/com/android/tv/settings/MainFragment.java
@@ -321,6 +321,12 @@
         }
 
         final Set<BluetoothDevice> bondedDevices = mBtAdapter.getBondedDevices();
+        if (bondedDevices == null) {
+            mAccessoriesGroup.setVisible(false);
+            mAccessoriesGroup.removeAll();
+            return;
+        }
+
         final Set<String> connectedBluetoothAddresses =
                 BluetoothConnectionsManager.getConnectedSet(getContext());
         final Context themedContext = getPreferenceManager().getContext();
@@ -331,26 +337,32 @@
         }
 
         for (final BluetoothDevice device : bondedDevices) {
-            final String desc = connectedBluetoothAddresses.contains(device.getAddress())
-                    ? getString(R.string.accessory_connected)
-                    : null;
+            final String deviceAddress = device.getAddress();
+            if (TextUtils.isEmpty(deviceAddress)) {
+                Log.w(TAG, "Skipping mysteriously empty bluetooth device");
+                continue;
+            }
 
-            final String key = "BluetoothDevice:" + device.getAddress();
+            final String key = "BluetoothDevice:" + deviceAddress;
             touchedKeys.add(key);
             Preference preference = mAccessoriesGroup.findPreference(key);
             if (preference == null) {
                 preference = new Preference(themedContext);
                 preference.setKey(key);
             }
-            preference.setTitle(device.getName());
+            final String deviceName = device.getName();
+            preference.setTitle(deviceName);
+            final String desc = connectedBluetoothAddresses.contains(deviceAddress)
+                    ? getString(R.string.accessory_connected)
+                    : null;
             preference.setSummary(desc);
             final int deviceImgId = AccessoryUtils.getImageIdForDevice(device);
             preference.setIcon(deviceImgId);
             preference.setFragment(BluetoothAccessoryFragment.class.getName());
             BluetoothAccessoryFragment.prepareArgs(
                     preference.getExtras(),
-                    device.getAddress(),
-                    device.getName(),
+                    deviceAddress,
+                    deviceName,
                     deviceImgId);
             mAccessoriesGroup.addPreference(preference);
         }
diff --git a/Settings/src/com/android/tv/settings/accessories/AccessoryUtils.java b/Settings/src/com/android/tv/settings/accessories/AccessoryUtils.java
index 5378873..33449c7 100644
--- a/Settings/src/com/android/tv/settings/accessories/AccessoryUtils.java
+++ b/Settings/src/com/android/tv/settings/accessories/AccessoryUtils.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
 
 import com.android.tv.settings.R;
 
@@ -26,8 +27,13 @@
  * Provide utilities for Remote & Accessories.
  */
 public class AccessoryUtils {
-    public static @DrawableRes int getImageIdForDevice(BluetoothDevice dev) {
-        int devClass = dev.getBluetoothClass().getDeviceClass();
+    public static @DrawableRes int getImageIdForDevice(@NonNull BluetoothDevice dev) {
+        final BluetoothClass bluetoothClass = dev.getBluetoothClass();
+        if (bluetoothClass == null) {
+            return R.drawable.ic_bluetooth;
+        }
+
+        final int devClass = bluetoothClass.getDeviceClass();
 
         if (devClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
             return R.drawable.ic_headset_mic;
diff --git a/Settings/src/com/android/tv/settings/accessories/AddAccessoryPreferenceFragment.java b/Settings/src/com/android/tv/settings/accessories/AddAccessoryPreferenceFragment.java
index dac821d..229bcfe 100644
--- a/Settings/src/com/android/tv/settings/accessories/AddAccessoryPreferenceFragment.java
+++ b/Settings/src/com/android/tv/settings/accessories/AddAccessoryPreferenceFragment.java
@@ -60,6 +60,10 @@
             screen.removeAll();
         }
 
+        if (devices == null) {
+            return;
+        }
+
         // Add entries for the discovered Bluetooth devices
         for (BluetoothDevice bt : devices) {
             final Preference preference = new Preference(themedContext);
diff --git a/Settings/src/com/android/tv/settings/accessories/BluetoothAccessoryFragment.java b/Settings/src/com/android/tv/settings/accessories/BluetoothAccessoryFragment.java
index bf66695..8a5e228 100644
--- a/Settings/src/com/android/tv/settings/accessories/BluetoothAccessoryFragment.java
+++ b/Settings/src/com/android/tv/settings/accessories/BluetoothAccessoryFragment.java
@@ -16,6 +16,7 @@
 
 package com.android.tv.settings.accessories;
 
+import android.app.Fragment;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothGatt;
@@ -41,6 +42,7 @@
 import com.android.tv.settings.R;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 
@@ -58,6 +60,7 @@
 
     private static final int UNPAIR_TIMEOUT = 5000;
 
+    private static final String ARG_DEVICE = "device";
     private static final String ARG_ACCESSORY_ADDRESS = "accessory_address";
     private static final String ARG_ACCESSORY_NAME = "accessory_name";
     private static final String ARG_ACCESSORY_ICON_ID = "accessory_icon_res";
@@ -82,30 +85,7 @@
     };
 
     // Broadcast Receiver for Bluetooth related events
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            BluetoothDevice device = intent
-                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            if (mUnpairing) {
-                if (mDevice.equals(device)) {
-                    // Done removing device, finish the activity
-                    mMsgHandler.removeCallbacks(mTimeoutRunnable);
-                    navigateBack();
-                }
-            }
-        }
-    };
-
-    // Internal message handler
-    private final Handler mMsgHandler = new Handler();
-
-    private final Runnable mTimeoutRunnable = new Runnable() {
-        @Override
-        public void run() {
-            navigateBack();
-        }
-    };
+    private BroadcastReceiver mBroadcastReceiver;
 
     public static BluetoothAccessoryFragment newInstance(String deviceAddress, String deviceName,
             int deviceImgId) {
@@ -141,11 +121,13 @@
 
         BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
         if (btAdapter != null) {
-            Set<BluetoothDevice> bondedDevices = btAdapter.getBondedDevices();
-            for (BluetoothDevice device : bondedDevices) {
-                if (mDeviceAddress.equals(device.getAddress())) {
-                    mDevice = device;
-                    break;
+            final Set<BluetoothDevice> bondedDevices = btAdapter.getBondedDevices();
+            if (bondedDevices != null) {
+                for (BluetoothDevice device : bondedDevices) {
+                    if (mDeviceAddress.equals(device.getAddress())) {
+                        mDevice = device;
+                        break;
+                    }
                 }
             }
         }
@@ -167,11 +149,11 @@
             mDeviceGatt = mDevice.connectGatt(getActivity(), true, new GattBatteryCallbacks());
         }
         // Set a broadcast receiver to let us know when the device has been removed
-        IntentFilter adapterIntentFilter = new IntentFilter();
+        final IntentFilter adapterIntentFilter = new IntentFilter();
         adapterIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+        mBroadcastReceiver = new UnpairReceiver(this, mDevice);
         getActivity().registerReceiver(mBroadcastReceiver, adapterIntentFilter);
         if (mDevice != null && mDevice.getBondState() == BluetoothDevice.BOND_NONE) {
-            mMsgHandler.removeCallbacks(mTimeoutRunnable);
             navigateBack();
         }
     }
@@ -207,7 +189,8 @@
         mUnpairPref = new Preference(themedContext);
         updateUnpairPref(mUnpairPref);
         mUnpairPref.setFragment(UnpairConfirmFragment.class.getName());
-        UnpairConfirmFragment.prepareArgs(mUnpairPref.getExtras(), mDeviceName, mDeviceImgId);
+        UnpairConfirmFragment.prepareArgs(
+                mUnpairPref.getExtras(), mDevice, mDeviceName, mDeviceImgId);
         screen.addPreference(mUnpairPref);
 
         mBatteryPref = new Preference(themedContext);
@@ -217,6 +200,11 @@
         setPreferenceScreen(screen);
     }
 
+    public void setUnpairing(boolean unpairing) {
+        mUnpairing = unpairing;
+        updateUnpairPref(mUnpairPref);
+    }
+
     private void updateUnpairPref(Preference pref) {
         if (mUnpairing) {
             pref.setTitle(R.string.accessory_unpairing);
@@ -233,37 +221,6 @@
         mHandler.post(mBailoutRunnable);
     }
 
-    private void unpairDevice() {
-        if (mDevice != null) {
-            int state = mDevice.getBondState();
-
-            if (state == BluetoothDevice.BOND_BONDING) {
-                mDevice.cancelBondProcess();
-            }
-
-            if (state != BluetoothDevice.BOND_NONE) {
-                mUnpairing = true;
-                // Set a timeout, just in case we don't receive the unpair notification we
-                // use to finish the activity
-                mMsgHandler.postDelayed(mTimeoutRunnable, UNPAIR_TIMEOUT);
-                final boolean successful = mDevice.removeBond();
-                if (successful) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Bluetooth device successfully unpaired.");
-                    }
-                    // set the dialog to a waiting state
-                    if (mUnpairPref != null) {
-                        updateUnpairPref(mUnpairPref);
-                    }
-                } else {
-                    Log.e(TAG, "Failed to unpair Bluetooth Device: " + mDevice.getName());
-                }
-            }
-        } else {
-            Log.e(TAG, "Bluetooth device not found. Address = " + mDeviceAddress);
-        }
-    }
-
     private class GattBatteryCallbacks extends BluetoothGattCallback {
         @Override
         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
@@ -317,7 +274,7 @@
             if (GATT_BATTERY_LEVEL_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) {
                 final int batteryLevel =
                         characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
-                mMsgHandler.post(new Runnable() {
+                mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         if (mBatteryPref != null && !mUnpairing) {
@@ -331,14 +288,96 @@
         }
     }
 
+    private static class UnpairReceiver extends BroadcastReceiver {
+
+        private final Fragment mFragment;
+        private final BluetoothDevice mDevice;
+
+        public UnpairReceiver(Fragment fragment, BluetoothDevice device) {
+            mFragment = fragment;
+            mDevice = device;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final BluetoothDevice device = intent
+                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                    BluetoothDevice.BOND_NONE);
+            if (bondState == BluetoothDevice.BOND_NONE && Objects.equals(mDevice, device)) {
+                // Device was removed, bail out of the fragment
+                if (mFragment instanceof BluetoothAccessoryFragment) {
+                    ((BluetoothAccessoryFragment) mFragment).navigateBack();
+                } else if (mFragment instanceof UnpairConfirmFragment) {
+                    ((UnpairConfirmFragment) mFragment).navigateBack();
+                } else {
+                    throw new IllegalStateException(
+                            "UnpairReceiver attached to wrong fragment class");
+                }
+            }
+        }
+    }
+
     public static class UnpairConfirmFragment extends GuidedStepFragment {
 
-        public static void prepareArgs(@NonNull Bundle args, String deviceName,
-                @DrawableRes int deviceImgId) {
+        private BluetoothDevice mDevice;
+        private BroadcastReceiver mBroadcastReceiver;
+        private final Handler mHandler = new Handler();
+
+        private Runnable mBailoutRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (isResumed() && !getFragmentManager().popBackStackImmediate()) {
+                    getActivity().onBackPressed();
+                }
+            }
+        };
+
+        private final Runnable mTimeoutRunnable = new Runnable() {
+            @Override
+            public void run() {
+                navigateBack();
+            }
+        };
+
+        public static void prepareArgs(@NonNull Bundle args, BluetoothDevice device,
+                String deviceName, @DrawableRes int deviceImgId) {
+            args.putParcelable(ARG_DEVICE, device);
             args.putString(ARG_ACCESSORY_NAME, deviceName);
             args.putInt(ARG_ACCESSORY_ICON_ID, deviceImgId);
         }
 
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            mDevice = getArguments().getParcelable(ARG_DEVICE);
+            super.onCreate(savedInstanceState);
+        }
+
+        @Override
+        public void onStart() {
+            super.onStart();
+            if (mDevice.getBondState() == BluetoothDevice.BOND_NONE) {
+                navigateBack();
+            }
+            final IntentFilter adapterIntentFilter = new IntentFilter();
+            adapterIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+            mBroadcastReceiver = new UnpairReceiver(this, mDevice);
+            getActivity().registerReceiver(mBroadcastReceiver, adapterIntentFilter);
+        }
+
+        @Override
+        public void onStop() {
+            super.onStop();
+            getActivity().unregisterReceiver(mBroadcastReceiver);
+        }
+
+        @Override
+        public void onDestroy() {
+            super.onDestroy();
+            mHandler.removeCallbacks(mTimeoutRunnable);
+            mHandler.removeCallbacks(mBailoutRunnable);
+        }
+
         @NonNull
         @Override
         public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
@@ -364,14 +403,45 @@
         @Override
         public void onGuidedActionClicked(GuidedAction action) {
             if (action.getId() == GuidedAction.ACTION_ID_OK) {
-                final BluetoothAccessoryFragment fragment =
-                        (BluetoothAccessoryFragment) getTargetFragment();
-                fragment.unpairDevice();
+                unpairDevice();
             } else if (action.getId() == GuidedAction.ACTION_ID_CANCEL) {
                 getFragmentManager().popBackStack();
             } else {
                 super.onGuidedActionClicked(action);
             }
         }
+
+        private void navigateBack() {
+            // need to post this to avoid recursing in the fragment manager.
+            mHandler.removeCallbacks(mBailoutRunnable);
+            mHandler.post(mBailoutRunnable);
+        }
+
+        private void unpairDevice() {
+            if (mDevice != null) {
+                int state = mDevice.getBondState();
+
+                if (state == BluetoothDevice.BOND_BONDING) {
+                    mDevice.cancelBondProcess();
+                }
+
+                if (state != BluetoothDevice.BOND_NONE) {
+                    ((BluetoothAccessoryFragment) getTargetFragment()).setUnpairing(true);
+                    // Set a timeout, just in case we don't receive the unpair notification we
+                    // use to finish the activity
+                    mHandler.postDelayed(mTimeoutRunnable, UNPAIR_TIMEOUT);
+                    final boolean successful = mDevice.removeBond();
+                    if (successful) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Bluetooth device successfully unpaired.");
+                        }
+                    } else {
+                        Log.e(TAG, "Failed to unpair Bluetooth Device: " + mDevice.getName());
+                    }
+                }
+            } else {
+                Log.e(TAG, "Bluetooth device not found. Address = " + mDevice.getAddress());
+            }
+        }
     }
 }
diff --git a/Settings/src/com/android/tv/settings/connectivity/WpsConnectionActivity.java b/Settings/src/com/android/tv/settings/connectivity/WpsConnectionActivity.java
index ea089aa..840c992 100644
--- a/Settings/src/com/android/tv/settings/connectivity/WpsConnectionActivity.java
+++ b/Settings/src/com/android/tv/settings/connectivity/WpsConnectionActivity.java
@@ -99,6 +99,10 @@
         setLayoutProperties(R.layout.setup_auth_activity, R.id.description, R.id.action);
         super.onCreate(savedInstanceState);
         mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+
+        if (ThemeHelper.fromSetupWizard(getIntent())) {
+            setTitle(getResources().getString(R.string.wifi_wps_title));
+        }
     }
 
     @Override
diff --git a/Settings/src/com/android/tv/settings/connectivity/setup/WifiSetupActivity.java b/Settings/src/com/android/tv/settings/connectivity/setup/WifiSetupActivity.java
index ccad5e1..38f864c 100644
--- a/Settings/src/com/android/tv/settings/connectivity/setup/WifiSetupActivity.java
+++ b/Settings/src/com/android/tv/settings/connectivity/setup/WifiSetupActivity.java
@@ -34,6 +34,7 @@
 import android.os.Message;
 import android.text.TextUtils;
 import android.util.Pair;
+import android.view.accessibility.AccessibilityEvent;
 
 import com.android.settingslib.wifi.WifiTracker;
 import com.android.tv.settings.R;
@@ -382,6 +383,9 @@
                 mWifiTracker.resumeScanning();
             }
         }
+        if (ThemeHelper.fromSetupWizard(getIntent())) {
+            updateTitle(formPageType);
+        }
     }
 
     @Override
@@ -532,4 +536,32 @@
             addPage(WifiFormPageType.SUMMARY_NOT_CONNECTED);
         }
     }
+
+    private void updateTitle(WifiFormPageType pageType) {
+        switch (pageType) {
+            // Fall through for all pageTypes that require the SSID of the network for
+            // the title.
+            case ADVANCED_OPTIONS:
+            case CONNECT:
+            case CONNECT_FAILED:
+            case CONNECT_TIMEOUT:
+            case ENTER_PASSWORD:
+            case KNOWN_NETWORK:
+            case SAVE:
+            case SAVE_FAILED:
+                setTitle(getResources().getString(pageType.getTitleResourceId(),
+                        mConfiguration.getPrintableSsid()));
+                break;
+            case WPS:
+                // Delegate title to the WPSConnectionActivity. Use blank string to prevent
+                // talkback from announcing a misplaced title.
+                setTitle("");
+                return;
+            default:
+                setTitle(getResources().getString(pageType.getTitleResourceId()));
+                break;
+        }
+        getWindow().getDecorView()
+                .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
 }