diff options
author | Jake Hamby <jhamby@google.com> | 2011-02-07 18:21:25 -0800 |
---|---|---|
committer | Jake Hamby <jhamby@google.com> | 2011-03-01 18:44:36 -0800 |
commit | 436b29e68e6608bed9e8e7d54385b8f62d89208e (patch) | |
tree | 60696619f093e2a9f8c9161804029b35d078a9bd /src/com/android | |
parent | 2adae4e274c00f0b05b405d60b8def23d9b28469 (diff) | |
download | packages_apps_Settings-436b29e68e6608bed9e8e7d54385b8f62d89208e.tar.gz packages_apps_Settings-436b29e68e6608bed9e8e7d54385b8f62d89208e.tar.bz2 packages_apps_Settings-436b29e68e6608bed9e8e7d54385b8f62d89208e.zip |
Refactor Bluetooth settings for readability and performance.
Major refactoring of Bluetooth settings classes.
- Moved all functionality from LocalBluetoothManager into new
LocalBluetoothAdapter and LocalBluetoothPreferences, and into
existing classes.
- Refactored functionality from BluetoothEventRedirector into new
BluetoothEventManager class, deleting the original version. New
version uses a HashMap from action Strings to implementers of the
BluetoothEventManager.Handler interface.
- Created new BluetoothDiscoveryReceiver to update shared preferences
timestamp for Bluetooth discovery start/finish. This is the only event
handling we need to do when the settings app is not visible, so it has
its own receiver entry in AndroidManifest.xml. Edits are written using
QueuedWork.singleThreadExecutor(), which BroadcastReceiver knows about
and will wait for completion, eliminating the need for PendingResult.
- Miscellaneous cleanups to code style and logic for readability.
- Pulled some large switch statement code blocks into new methods.
- Changed all Bluetooth state references to the new BluetoothProfile
constants.
- Changed use of deprecated Notification constructor in
BluetoothPairingRequest to use Notification.Builder.
- Moved Utf8ByteLengthFilter helper function from BluetoothNamePreference
into its own class, and moved test cases into the same package.
- Moved all LocalBluetoothProfileManager functionality related to
specific profiles into new top-level classes (A2dpProfile, etc.), all
implementing the LocalBluetoothProfile interface.
- Moved all UI-related methods from CachedBluetoothDevice into the class
that uses the method, or into the static Utils class for shared methods.
Change-Id: I6d49b7f4ae0c7d7dcf62551ee40b51ecb5fe4f47
Diffstat (limited to 'src/com/android')
40 files changed, 3503 insertions, 3018 deletions
diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java index 2ff32541d..af831576b 100644 --- a/src/com/android/settings/TetherSettings.java +++ b/src/com/android/settings/TetherSettings.java @@ -35,8 +35,6 @@ import android.os.Environment; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceScreen; -import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.webkit.WebView; @@ -49,7 +47,6 @@ import java.util.Locale; * Displays preferences for Tethering. */ public class TetherSettings extends SettingsPreferenceFragment { - private static final String TAG = "TetheringSettings"; private static final String USB_TETHER_SETTINGS = "usb_tether_settings"; private static final String ENABLE_WIFI_AP = "enable_wifi_ap"; @@ -66,8 +63,6 @@ public class TetherSettings extends SettingsPreferenceFragment { private WebView mView; private CheckBoxPreference mUsbTether; - private CheckBoxPreference mEnableWifiAp; - private PreferenceScreen mWifiApSettings; private WifiApEnabler mWifiApEnabler; private CheckBoxPreference mBluetoothTether; @@ -95,9 +90,9 @@ public class TetherSettings extends SettingsPreferenceFragment { BluetoothProfile.PAN); } - - mEnableWifiAp = (CheckBoxPreference) findPreference(ENABLE_WIFI_AP); - mWifiApSettings = (PreferenceScreen) findPreference(WIFI_AP_SETTINGS); + CheckBoxPreference enableWifiAp = + (CheckBoxPreference) findPreference(ENABLE_WIFI_AP); + Preference wifiApSettings = findPreference(WIFI_AP_SETTINGS); mUsbTether = (CheckBoxPreference) findPreference(USB_TETHER_SETTINGS); mBluetoothTether = (CheckBoxPreference) findPreference(ENABLE_BLUETOOTH_TETHERING); mTetherHelp = (PreferenceScreen) findPreference(TETHERING_HELP); @@ -118,8 +113,8 @@ public class TetherSettings extends SettingsPreferenceFragment { } if (!wifiAvailable) { - getPreferenceScreen().removePreference(mEnableWifiAp); - getPreferenceScreen().removePreference(mWifiApSettings); + getPreferenceScreen().removePreference(enableWifiAp); + getPreferenceScreen().removePreference(wifiApSettings); } if (!bluetoothAvailable) { @@ -132,7 +127,7 @@ public class TetherSettings extends SettingsPreferenceFragment { } } - mWifiApEnabler = new WifiApEnabler(activity, mEnableWifiAp); + mWifiApEnabler = new WifiApEnabler(activity, enableWifiAp); mView = new WebView(activity); } @@ -154,22 +149,22 @@ public class TetherSettings extends SettingsPreferenceFragment { // check for the full language + country resource, if not there, try just language final AssetManager am = getActivity().getAssets(); String path = HELP_PATH.replace("%y", locale.getLanguage().toLowerCase()); - path = path.replace("%z", "_"+locale.getCountry().toLowerCase()); + path = path.replace("%z", '_'+locale.getCountry().toLowerCase()); boolean useCountry = true; InputStream is = null; try { is = am.open(path); - } catch (Exception e) { + } catch (Exception ignored) { useCountry = false; } finally { if (is != null) { try { is.close(); - } catch (Exception e) {} + } catch (Exception ignored) {} } } String url = HELP_URL.replace("%y", locale.getLanguage().toLowerCase()); - url = url.replace("%z", (useCountry ? "_"+locale.getCountry().toLowerCase() : "")); + url = url.replace("%z", useCountry ? '_'+locale.getCountry().toLowerCase() : ""); if ((mUsbRegexs.length != 0) && (mWifiRegexs.length == 0)) { url = url.replace("%x", USB_HELP_MODIFIER); } else if ((mWifiRegexs.length != 0) && (mUsbRegexs.length == 0)) { @@ -271,10 +266,8 @@ public class TetherSettings extends SettingsPreferenceFragment { String[] errored) { ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); - boolean usbTethered = false; boolean usbAvailable = false; int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR; - boolean usbErrored = false; boolean massStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState()); for (String s : available) { @@ -287,11 +280,13 @@ public class TetherSettings extends SettingsPreferenceFragment { } } } + boolean usbTethered = false; for (String s : tethered) { for (String regex : mUsbRegexs) { if (s.matches(regex)) usbTethered = true; } } + boolean usbErrored = false; for (String s: errored) { for (String regex : mUsbRegexs) { if (s.matches(regex)) usbErrored = true; @@ -329,25 +324,23 @@ public class TetherSettings extends SettingsPreferenceFragment { String[] errored) { ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); - boolean bluetoothTethered = false; - boolean bluetoothAvailable = false; int bluetoothError = ConnectivityManager.TETHER_ERROR_NO_ERROR; - boolean bluetoothErrored = false; for (String s : available) { for (String regex : mBluetoothRegexs) { if (s.matches(regex)) { - bluetoothAvailable = true; if (bluetoothError == ConnectivityManager.TETHER_ERROR_NO_ERROR) { bluetoothError = cm.getLastTetherError(s); } } } } + boolean bluetoothTethered = false; for (String s : tethered) { for (String regex : mBluetoothRegexs) { if (s.matches(regex)) bluetoothTethered = true; } } + boolean bluetoothErrored = false; for (String s: errored) { for (String regex : mBluetoothRegexs) { if (s.matches(regex)) bluetoothErrored = true; @@ -458,7 +451,7 @@ public class TetherSettings extends SettingsPreferenceFragment { return super.onPreferenceTreeClick(screen, preference); } - private String findIface(String[] ifaces, String[] regexes) { + private static String findIface(String[] ifaces, String[] regexes) { for (String iface : ifaces) { for (String regex : regexes) { if (iface.matches(regex)) { diff --git a/src/com/android/settings/WirelessSettings.java b/src/com/android/settings/WirelessSettings.java index b1b7e6ec1..4b927491c 100644 --- a/src/com/android/settings/WirelessSettings.java +++ b/src/com/android/settings/WirelessSettings.java @@ -19,6 +19,8 @@ package com.android.settings; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; import com.android.settings.bluetooth.BluetoothEnabler; +import com.android.settings.bluetooth.LocalBluetoothAdapter; +import com.android.settings.bluetooth.LocalBluetoothManager; import com.android.settings.wifi.WifiEnabler; import com.android.settings.nfc.NfcEnabler; @@ -103,7 +105,8 @@ public class WirelessSettings extends SettingsPreferenceFragment { mAirplaneModeEnabler = new AirplaneModeEnabler(activity, airplane); mAirplaneModePreference = (CheckBoxPreference) findPreference(KEY_TOGGLE_AIRPLANE); mWifiEnabler = new WifiEnabler(activity, wifi); - mBtEnabler = new BluetoothEnabler(activity, bt); + mBtEnabler = new BluetoothEnabler(activity, + LocalBluetoothManager.getInstance(activity).getBluetoothAdapter(), bt); mNfcEnabler = new NfcEnabler(activity, nfc); String toggleable = Settings.System.getString(activity.getContentResolver(), diff --git a/src/com/android/settings/bluetooth/A2dpProfile.java b/src/com/android/settings/bluetooth/A2dpProfile.java new file mode 100644 index 000000000..96225d87c --- /dev/null +++ b/src/com/android/settings/bluetooth/A2dpProfile.java @@ -0,0 +1,166 @@ +/* + * 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.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; + +import com.android.settings.R; + +import java.util.List; + +/** + * A2dpProfile handles Bluetooth A2DP. + * TODO: add null checks around calls to mService object. + */ +final class A2dpProfile implements LocalBluetoothProfile { + private BluetoothA2dp mService; + + static final ParcelUuid[] SINK_UUIDS = { + BluetoothUuid.AudioSink, + BluetoothUuid.AdvAudioDist, + }; + + static final String NAME = "A2DP"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 1; + + // These callbacks run on the main thread. + private final class A2dpServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mService = (BluetoothA2dp) proxy; + } + + public void onServiceDisconnected(int profile) { + mService = null; + } + } + + A2dpProfile(Context context) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + adapter.getProfileProxy(context, new A2dpServiceListener(), + BluetoothProfile.A2DP); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + private List<BluetoothDevice> getConnectedDevices() { + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public boolean connect(BluetoothDevice device) { + List<BluetoothDevice> sinks = getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + boolean isA2dpPlaying() { + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (!sinks.isEmpty()) { + if (mService.isA2dpPlaying(sinks.get(0))) { + return true; + } + } + return false; + } + + public boolean isProfileReady() { + return mService != null; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_a2dp; + } + + public int getDisconnectResource() { + return R.string.bluetooth_disconnect_a2dp_profile; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = mService.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_a2dp_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_a2dp_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_headphones_a2dp; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothCallback.java b/src/com/android/settings/bluetooth/BluetoothCallback.java new file mode 100644 index 000000000..3ce9adfef --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothCallback.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * BluetoothCallback provides a callback interface for the settings + * UI to receive events from {@link BluetoothEventManager}. + */ +interface BluetoothCallback { + void onBluetoothStateChanged(int bluetoothState); + void onScanningStateChanged(boolean started); + void onDeviceAdded(CachedBluetoothDevice cachedDevice); + void onDeviceDeleted(CachedBluetoothDevice cachedDevice); + void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState); +} diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java new file mode 100644 index 000000000..00e342ced --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDeviceFilter.java @@ -0,0 +1,159 @@ +/* + * 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.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.util.Log; + +/** + * BluetoothDeviceFilter contains a static method that returns a + * Filter object that returns whether or not the BluetoothDevice + * passed to it matches the specified filter type constant from + * {@link android.bluetooth.BluetoothDevicePicker}. + */ +final class BluetoothDeviceFilter { + private static final String TAG = "BluetoothDeviceFilter"; + + /** The filter interface to external classes. */ + interface Filter { + boolean matches(BluetoothDevice device); + } + + /** All filter singleton (referenced directly). */ + static final Filter ALL_FILTER = new AllFilter(); + + /** Bonded devices only filter (referenced directly). */ + static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter(); + + /** Table of singleton filter objects. */ + private static final Filter[] FILTERS = { + ALL_FILTER, // FILTER_TYPE_ALL + new AudioFilter(), // FILTER_TYPE_AUDIO + new TransferFilter(), // FILTER_TYPE_TRANSFER + new PanuFilter(), // FILTER_TYPE_PANU + new NapFilter() // FILTER_TYPE_NAP + }; + + /** Private constructor. */ + private BluetoothDeviceFilter() { + } + + /** + * Returns the singleton {@link Filter} object for the specified type, + * or {@link #ALL_FILTER} if the type value is out of range. + * + * @param filterType a constant from BluetoothDevicePicker + * @return a singleton object implementing the {@link Filter} interface. + */ + static Filter getFilter(int filterType) { + if (filterType >= 0 && filterType < FILTERS.length) { + return FILTERS[filterType]; + } else { + Log.w(TAG, "Invalid filter type " + filterType + " for device picker"); + return ALL_FILTER; + } + } + + /** Filter that matches all devices. */ + private static final class AllFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return true; + } + } + + /** Filter that matches only bonded devices. */ + private static final class BondedDeviceFilter 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); + + public boolean matches(BluetoothDevice device) { + return matches(device.getUuids(), device.getBluetoothClass()); + } + } + + /** Filter that matches devices that support AUDIO profiles. */ + private static final class AudioFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) { + return true; + } + if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) { + return true; + } + } else if (btClass != null) { + if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) || + btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + return true; + } + } + return false; + } + } + + /** Filter that matches devices that support Object Transfer. */ + private static final class TransferFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP); + } + } + + /** Filter that matches devices that support PAN User (PANU) profile. */ + private static final class PanuFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU); + } + } + + /** Filter that matches devices that support NAP profile. */ + private static final class NapFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_NAP); + } + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java index 6a0b8811c..5f791d942 100644 --- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java @@ -16,27 +16,32 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - +import android.app.AlertDialog; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.content.DialogInterface; import android.graphics.drawable.Drawable; import android.preference.Preference; +import android.text.TextUtils; +import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.view.View.OnClickListener; +import android.view.ViewGroup; import android.widget.ImageView; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; +import com.android.settings.R; -import java.util.Map; +import java.util.List; /** * BluetoothDevicePreference is the preference type used to display each remote * Bluetooth device in the Bluetooth Settings screen. */ -public class BluetoothDevicePreference extends Preference implements +public final class BluetoothDevicePreference extends Preference implements CachedBluetoothDevice.Callback, OnClickListener { private static final String TAG = "BluetoothDevicePreference"; @@ -48,11 +53,7 @@ public class BluetoothDevicePreference extends Preference implements private OnClickListener mOnSettingsClickListener; - /** - * Cached local copy of whether the device is busy. This is only updated - * from {@link #onDeviceAttributesChanged()}. - */ - private boolean mIsBusy; + private AlertDialog mDisconnectDialog; public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) { super(context); @@ -60,19 +61,19 @@ public class BluetoothDevicePreference extends Preference implements if (sDimAlpha == Integer.MIN_VALUE) { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true); - sDimAlpha = (int) ((outValue.getFloat() * 255) * 0.5); + sDimAlpha = (int) (outValue.getFloat() * 255); } mCachedDevice = cachedDevice; setWidgetLayoutResource(R.layout.preference_bluetooth); - cachedDevice.registerCallback(this); + mCachedDevice.registerCallback(this); onDeviceAttributesChanged(); } - public CachedBluetoothDevice getCachedDevice() { + CachedBluetoothDevice getCachedDevice() { return mCachedDevice; } @@ -84,42 +85,30 @@ public class BluetoothDevicePreference extends Preference implements protected void onPrepareForRemoval() { super.onPrepareForRemoval(); mCachedDevice.unregisterCallback(this); + if (mDisconnectDialog != null) { + mDisconnectDialog.dismiss(); + mDisconnectDialog = null; + } } public void onDeviceAttributesChanged() { - /* * The preference framework takes care of making sure the value has - * changed before proceeding. + * changed before proceeding. It will also call notifyChanged() if + * any preference info has changed from the previous value. */ - setTitle(mCachedDevice.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(mCachedDevice.getSummary()); + setSummary(getConnectionSummary()); // Used to gray out the item - mIsBusy = mCachedDevice.isBusy(); - - // Data has changed - notifyChanged(); + setEnabled(!mCachedDevice.isBusy()); - // This could affect ordering, so notify that also + // This could affect ordering, so notify that notifyHierarchyChanged(); } @Override - public boolean isEnabled() { - // Temp fix until we have 2053751 fixed in the framework - setEnabled(true); - return super.isEnabled() && !mIsBusy; - } - - @Override protected void onBindView(View view) { // Disable this view if the bluetooth enable/disable preference view is off if (null != findPreferenceInHierarchy("bt_checkbox")) { @@ -129,17 +118,17 @@ public class BluetoothDevicePreference extends Preference implements super.onBindView(view); ImageView btClass = (ImageView) view.findViewById(android.R.id.icon); - btClass.setImageResource(mCachedDevice.getBtClassDrawable()); - btClass.setAlpha(!mIsBusy ? 255 : sDimAlpha); + btClass.setImageResource(getBtClassDrawable()); + btClass.setAlpha(isEnabled() ? 255 : sDimAlpha); mDeviceSettings = (ImageView) view.findViewById(R.id.deviceDetails); if (mOnSettingsClickListener != null) { mDeviceSettings.setOnClickListener(this); mDeviceSettings.setTag(mCachedDevice); - mDeviceSettings.setAlpha(!mIsBusy ? 255 : sDimAlpha); + mDeviceSettings.setAlpha(isEnabled() ? 255 : sDimAlpha); } else { // Hide the settings icon and divider mDeviceSettings.setVisibility(View.GONE); - ImageView divider = (ImageView) view.findViewById(R.id.divider); + View divider = view.findViewById(R.id.divider); if (divider != null) { divider.setVisibility(View.GONE); } @@ -148,24 +137,40 @@ public class BluetoothDevicePreference extends Preference implements LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); ViewGroup profilesGroup = (ViewGroup) view.findViewById(R.id.profileIcons); - Map<Profile, Drawable> profileIcons = mCachedDevice.getProfileIcons(); - for (Profile profile : profileIcons.keySet()) { - Drawable icon = profileIcons.get(profile); - inflater.inflate(R.layout.profile_icon_small, profilesGroup, true); - ImageView imageView = - (ImageView) profilesGroup.getChildAt(profilesGroup.getChildCount() - 1); - imageView.setImageDrawable(icon); - boolean profileEnabled = mCachedDevice.isConnectedProfile(profile); - imageView.setAlpha(profileEnabled ? 255 : sDimAlpha); + for (LocalBluetoothProfile profile : mCachedDevice.getProfiles()) { + int iconResource = profile.getDrawableResource(mCachedDevice.getBtClass()); + if (iconResource != 0) { + Drawable icon = getContext().getResources().getDrawable(iconResource); + inflater.inflate(R.layout.profile_icon_small, profilesGroup, true); + ImageView imageView = + (ImageView) profilesGroup.getChildAt(profilesGroup.getChildCount() - 1); + imageView.setImageDrawable(icon); + boolean profileEnabled = mCachedDevice.isConnectedProfile(profile); + imageView.setAlpha(profileEnabled ? 255 : sDimAlpha); + } } } public void onClick(View v) { if (v == mDeviceSettings) { - if (mOnSettingsClickListener != null) mOnSettingsClickListener.onClick(v); + if (mOnSettingsClickListener != null) { + mOnSettingsClickListener.onClick(v); + } } } + public boolean equals(Object o) { + if ((o == null) || !(o instanceof BluetoothDevicePreference)) { + return false; + } + return mCachedDevice.equals( + ((BluetoothDevicePreference) o).mCachedDevice); + } + + public int hashCode() { + return mCachedDevice.hashCode(); + } + @Override public int compareTo(Preference another) { if (!(another instanceof BluetoothDevicePreference)) { @@ -173,7 +178,112 @@ public class BluetoothDevicePreference extends Preference implements return 1; } - return mCachedDevice.compareTo(((BluetoothDevicePreference) another).mCachedDevice); + return mCachedDevice + .compareTo(((BluetoothDevicePreference) another).mCachedDevice); + } + + void onClicked() { + int bondState = mCachedDevice.getBondState(); + + if (mCachedDevice.isConnected()) { + askDisconnect(); + } else if (bondState == BluetoothDevice.BOND_BONDED) { + mCachedDevice.connect(true); + } else if (bondState == BluetoothDevice.BOND_NONE) { + pair(); + } + } + + // Show disconnect confirmation dialog for a device. + private void askDisconnect() { + Context context = getContext(); + String name = mCachedDevice.getName(); + if (TextUtils.isEmpty(name)) { + name = context.getString(R.string.bluetooth_device); + } + String message = context.getString(R.string.bluetooth_disconnect_blank, name); + + DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mCachedDevice.disconnect(); + } + }; + + mDisconnectDialog = Utils.showDisconnectDialog(context, + mDisconnectDialog, disconnectListener, name, message); + } + + private void pair() { + if (!mCachedDevice.startPairing()) { + Utils.showError(getContext(), mCachedDevice.getName(), + R.string.bluetooth_pairing_error_message); + } + } + + private int getConnectionSummary() { + final CachedBluetoothDevice cachedDevice = mCachedDevice; + final BluetoothDevice device = cachedDevice.getDevice(); + + // if any profiles are connected or busy, return that status + for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) { + int connectionStatus = profile.getConnectionStatus(device); + + if (connectionStatus != BluetoothProfile.STATE_DISCONNECTED) { + return Utils.getConnectionStateSummary(connectionStatus); + } + } + + switch (cachedDevice.getBondState()) { + case BluetoothDevice.BOND_BONDED: + return R.string.bluetooth_paired; + case BluetoothDevice.BOND_BONDING: + return R.string.bluetooth_pairing; + case BluetoothDevice.BOND_NONE: + return R.string.bluetooth_not_connected; + default: + return 0; + } } + private int getBtClassDrawable() { + BluetoothClass btClass = mCachedDevice.getBtClass(); + if (btClass != null) { + switch (btClass.getMajorDeviceClass()) { + case BluetoothClass.Device.Major.COMPUTER: + return R.drawable.ic_bt_laptop; + + case BluetoothClass.Device.Major.PHONE: + return R.drawable.ic_bt_cellphone; + + case BluetoothClass.Device.Major.PERIPHERAL: + return HidProfile.getHidClassDrawable(btClass); + + case BluetoothClass.Device.Major.IMAGING: + return R.drawable.ic_bt_imaging; + + default: + // unrecognized device class; continue + } + } else { + Log.w(TAG, "mBtClass is null"); + } + + List<LocalBluetoothProfile> profiles = mCachedDevice.getProfiles(); + for (LocalBluetoothProfile profile : profiles) { + int resId = profile.getDrawableResource(btClass); + if (resId != 0) { + return resId; + } + } + if (btClass != null) { + if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { + return R.drawable.ic_bt_headphones_a2dp; + + } + if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + return R.drawable.ic_bt_headset_hfp; + } + } + return 0; + } } diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java index 166e4aea0..40bf5bc3a 100644 --- a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java @@ -16,41 +16,34 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - import android.bluetooth.BluetoothAdapter; 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.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; -import android.preference.CheckBoxPreference; -import android.provider.Settings; -import android.util.Log; + +import com.android.settings.R; /** * 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"; +final class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChangeListener { private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT = "debug.bt.discoverable_time"; - static final int DISCOVERABLE_TIMEOUT_TWO_MINUTES = 120; - static final int DISCOVERABLE_TIMEOUT_FIVE_MINUTES = 300; - static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600; + private static final int DISCOVERABLE_TIMEOUT_TWO_MINUTES = 120; + private static final int DISCOVERABLE_TIMEOUT_FIVE_MINUTES = 300; + private static final int DISCOVERABLE_TIMEOUT_ONE_HOUR = 3600; static final int DISCOVERABLE_TIMEOUT_NEVER = 0; - static final String SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP = - "discoverable_end_timestamp"; - private static final String VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES = "twomin"; private static final String VALUE_DISCOVERABLE_TIMEOUT_FIVE_MINUTES = "fivemin"; private static final String VALUE_DISCOVERABLE_TIMEOUT_ONE_HOUR = "onehour"; @@ -63,7 +56,7 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan private final CheckBoxPreference mCheckBoxPreference; private final ListPreference mTimeoutListPreference; - private final LocalBluetoothManager mLocalManager; + private final LocalBluetoothAdapter mLocalAdapter; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -84,7 +77,7 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan } }; - public BluetoothDiscoverableEnabler(Context context, + BluetoothDiscoverableEnabler(Context context, LocalBluetoothAdapter adapter, CheckBoxPreference checkBoxPreference, ListPreference timeoutListPreference) { mContext = context; mUiHandler = new Handler(); @@ -95,15 +88,15 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan // we actually want to persist this since can't infer from BT device state mTimeoutListPreference.setPersistent(true); - mLocalManager = LocalBluetoothManager.getInstance(context); - if (mLocalManager == null) { + mLocalAdapter = adapter; + if (adapter == null) { // Bluetooth not supported checkBoxPreference.setEnabled(false); } } public void resume() { - if (mLocalManager == null) { + if (mLocalAdapter == null) { return; } @@ -111,11 +104,11 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan mContext.registerReceiver(mReceiver, filter); mCheckBoxPreference.setOnPreferenceChangeListener(this); mTimeoutListPreference.setOnPreferenceChangeListener(this); - handleModeChanged(mLocalManager.getBluetoothAdapter().getScanMode()); + handleModeChanged(mLocalAdapter.getScanMode()); } public void pause() { - if (mLocalManager == null) { + if (mLocalAdapter == null) { return; } @@ -137,43 +130,37 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan return true; } - private void setEnabled(final boolean enable) { - BluetoothAdapter manager = mLocalManager.getBluetoothAdapter(); - + private void setEnabled(boolean enable) { if (enable) { int timeout = getDiscoverableTimeout(); - manager.setDiscoverableTimeout(timeout); + mLocalAdapter.setDiscoverableTimeout(timeout); long endTimestamp = System.currentTimeMillis() + timeout * 1000L; - persistDiscoverableEndTimestamp(endTimestamp); + LocalBluetoothPreferences.persistDiscoverableEndTimestamp(mContext, endTimestamp); updateCountdownSummary(); - manager.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout); + mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout); } else { - manager.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); } } private void updateTimerDisplay(int timeout) { if (getDiscoverableTimeout() == DISCOVERABLE_TIMEOUT_NEVER) { mCheckBoxPreference.setSummaryOn( - mContext.getResources() - .getString(R.string.bluetooth_is_discoverable_always)); + mContext.getString(R.string.bluetooth_is_discoverable_always)); } else { mCheckBoxPreference.setSummaryOn( - mContext.getResources() - .getString(R.string.bluetooth_is_discoverable, timeout)); + mContext.getString(R.string.bluetooth_is_discoverable, timeout)); } } private int getDiscoverableTimeout() { int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1); if (timeout < 0) { - String timeoutValue = null; - if (mTimeoutListPreference != null && mTimeoutListPreference.getValue() != null) { - timeoutValue = mTimeoutListPreference.getValue().toString(); - } else { + String timeoutValue = mTimeoutListPreference.getValue(); + if (timeoutValue == null) { mTimeoutListPreference.setValue(VALUE_DISCOVERABLE_TIMEOUT_TWO_MINUTES); return DISCOVERABLE_TIMEOUT_TWO_MINUTES; } @@ -192,12 +179,6 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan return timeout; } - private void persistDiscoverableEndTimestamp(long endTimestamp) { - SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit(); - editor.putLong(SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp); - editor.apply(); - } - private void handleModeChanged(int mode) { if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { mCheckBoxPreference.setChecked(true); @@ -208,14 +189,13 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan } private void updateCountdownSummary() { - int mode = mLocalManager.getBluetoothAdapter().getScanMode(); + int mode = mLocalAdapter.getScanMode(); if (mode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { return; } long currentTimestamp = System.currentTimeMillis(); - long endTimestamp = mLocalManager.getSharedPreferences().getLong( - SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0); + long endTimestamp = LocalBluetoothPreferences.getDiscoverableEndTimestamp(mContext); if (currentTimestamp > endTimestamp) { // We're still in discoverable mode, but maybe there isn't a timeout. @@ -231,6 +211,4 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan mUiHandler.postDelayed(mUpdateCountdownSummaryRunnable, 1000); } } - - } diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java b/src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java new file mode 100644 index 000000000..fbb682756 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothDiscoveryReceiver.java @@ -0,0 +1,47 @@ +/* + * 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.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +/** + * BluetoothDiscoveryReceiver updates a timestamp when the + * Bluetooth adapter starts or finishes discovery mode. This + * is used to decide whether to open an alert dialog or + * create a notification when we receive a pairing request. + * + * <p>Note that the discovery start/finish intents are also handled + * by {@link BluetoothEventManager} to update the UI, if visible. + */ +public final class BluetoothDiscoveryReceiver extends BroadcastReceiver { + private static final String TAG = "BluetoothDiscoveryReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.v(TAG, "Received: " + action); + + if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED) || + action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { + LocalBluetoothPreferences.persistDiscoveringTimestamp(context); + } + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java index a7b9d713e..9aeb1b939 100644 --- a/src/com/android/settings/bluetooth/BluetoothEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java @@ -34,13 +34,14 @@ import android.widget.Toast; * preference. It turns on/off Bluetooth and ensures the summary of the * preference reflects the current state. */ -public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { +public final class BluetoothEnabler implements Preference.OnPreferenceChangeListener { private final Context mContext; private final CheckBoxPreference mCheckBox; private final CharSequence mOriginalSummary; - private final LocalBluetoothManager mLocalManager; + private final LocalBluetoothAdapter mLocalAdapter; private final IntentFilter mIntentFilter; + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -49,14 +50,15 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { } }; - public BluetoothEnabler(Context context, CheckBoxPreference checkBox) { + public BluetoothEnabler(Context context, LocalBluetoothAdapter adapter, + CheckBoxPreference checkBox) { mContext = context; mCheckBox = checkBox; mOriginalSummary = checkBox.getSummary(); checkBox.setPersistent(false); - mLocalManager = LocalBluetoothManager.getInstance(context); - if (mLocalManager == null) { + mLocalAdapter = adapter; + if (adapter == null) { // Bluetooth is not supported checkBox.setEnabled(false); } @@ -64,19 +66,19 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { } public void resume() { - if (mLocalManager == null) { + if (mLocalAdapter == null) { return; } // Bluetooth state is not sticky, so set it manually - handleStateChanged(mLocalManager.getBluetoothState()); + handleStateChanged(mLocalAdapter.getBluetoothState()); mContext.registerReceiver(mReceiver, mIntentFilter); mCheckBox.setOnPreferenceChangeListener(this); } public void pause() { - if (mLocalManager == null) { + if (mLocalAdapter == null) { return; } @@ -95,14 +97,14 @@ public class BluetoothEnabler implements Preference.OnPreferenceChangeListener { return false; } - mLocalManager.setBluetoothEnabled(enable); + mLocalAdapter.setBluetoothEnabled(enable); mCheckBox.setEnabled(false); // Don't update UI to opposite state until we're sure return false; } - /* package */ void handleStateChanged(int state) { + void handleStateChanged(int state) { switch (state) { case BluetoothAdapter.STATE_TURNING_ON: mCheckBox.setSummary(R.string.wifi_starting); diff --git a/src/com/android/settings/bluetooth/BluetoothEventManager.java b/src/com/android/settings/bluetooth/BluetoothEventManager.java new file mode 100644 index 000000000..70e35f984 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothEventManager.java @@ -0,0 +1,398 @@ +/* + * 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 com.android.settings.R; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth + * API and dispatches the event on the UI thread to the right class in the + * Settings. + */ +final class BluetoothEventManager { + private static final String TAG = "BluetoothEventManager"; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private LocalBluetoothProfileManager mProfileManager; + private final IntentFilter mIntentFilter; + private final Map<String, Handler> mHandlerMap; + + private final Collection<BluetoothCallback> mCallbacks = + new ArrayList<BluetoothCallback>(); + + interface Handler { + void onReceive(Context context, Intent intent, BluetoothDevice device); + } + + void addHandler(String action, Handler handler) { + mHandlerMap.put(action, handler); + mIntentFilter.addAction(action); + } + + // Set profile manager after construction due to circular dependency + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + BluetoothEventManager(LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mIntentFilter = new IntentFilter(); + mHandlerMap = new HashMap<String, Handler>(); + + // Bluetooth on/off broadcasts + addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler()); + + // Discovery broadcasts + addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true)); + addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false)); + addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler()); + addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler()); + addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); + + // Pairing broadcasts + addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler()); + addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler()); + + // Fine-grained state broadcasts + addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler()); + addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler()); + + // Dock event broadcasts + addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler()); + } + + /** + * A Bluetooth-related activity is now in the foreground. Register to + * start receiving Bluetooth events. + * @param context a Context object for the current Activity + */ + void resume(Context context) { + if (mLocalAdapter.syncBluetoothState()) { + // adapter state changed while we were paused: send callbacks + int newState = mLocalAdapter.getState(); + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onBluetoothStateChanged(newState); + } + } + } + context.registerReceiver(mBroadcastReceiver, mIntentFilter); + } + + void pause(Context context) { + context.unregisterReceiver(mBroadcastReceiver); + } + + /** Register to start receiving callbacks for Bluetooth events. */ + void registerCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.add(callback); + } + } + + /** Unregister to stop receiving callbacks for Bluetooth events. */ + void unregisterCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + // This can't be called from a broadcast receiver where the filter is set in the Manifest. + private static String getDockedDeviceAddress(Context context) { + // This works only because these broadcast intents are "sticky" + Intent i = context.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (i != null) { + int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device != null) { + return device.getAddress(); + } + } + } + return null; + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.v(TAG, "Received " + intent.getAction()); + + String action = intent.getAction(); + BluetoothDevice device = intent + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + Handler handler = mHandlerMap.get(action); + if (handler != null) { + handler.onReceive(context, intent, device); + } + } + }; + + private class AdapterStateChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + // update local profiles and get paired devices + mLocalAdapter.setBluetoothStateInt(state); + // send callback to update UI and possibly start scanning + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onBluetoothStateChanged(state); + } + } + } + } + + private class ScanningStateChangedHandler implements Handler { + private final boolean mStarted; + + ScanningStateChangedHandler(boolean started) { + mStarted = started; + } + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onScanningStateChanged(mStarted); + } + } + mDeviceManager.onScanningStateChanged(mStarted); + LocalBluetoothPreferences.persistDiscoveringTimestamp(context); + } + } + + private class DeviceFoundHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); + BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); + String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); + // TODO Pick up UUID. They should be available for 2.1 devices. + // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); + Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " + + cachedDevice); + // callback to UI to create Preference for new device + dispatchDeviceAdded(cachedDevice); + } + cachedDevice.setRssi(rssi); + cachedDevice.setBtClass(btClass); + cachedDevice.setName(name); + cachedDevice.setVisible(true); + } + } + + private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceAdded(cachedDevice); + } + } + } + + private class DeviceDisappearedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); + return; + } + if (mDeviceManager.onDeviceDisappeared(cachedDevice)) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceDeleted(cachedDevice); + } + } + } + } + } + + private class NameChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onDeviceNameUpdated(device); + } + } + + private class BondStateChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + return; + } + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "CachedBluetoothDevice for device " + device + + " not found, calling readPairedDevices()."); + if (!readPairedDevices()) { + Log.e(TAG, "Got bonding state changed for " + device + + ", but we have no record of that device."); + return; + } + cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.e(TAG, "Got bonding state changed for " + device + + ", but device not added in cache."); + return; + } + } + + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceBondStateChanged(cachedDevice, bondState); + } + } + cachedDevice.onBondingStateChanged(bondState); + + if (bondState == BluetoothDevice.BOND_NONE) { + if (device.isBluetoothDock()) { + // After a dock is unpaired, we will forget the settings + LocalBluetoothPreferences + .removeDockAutoConnectSetting(context, device.getAddress()); + + // if the device is undocked, remove it from the list as well + if (!device.getAddress().equals(getDockedDeviceAddress(context))) { + mDeviceManager.onDeviceDisappeared(cachedDevice); + } + } + int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, + BluetoothDevice.ERROR); + + showUnbondMessage(context, cachedDevice.getName(), reason); + } + } + + /** + * Called when we have reached the unbonded state. + * + * @param reason one of the error reasons from + * BluetoothDevice.UNBOND_REASON_* + */ + private void showUnbondMessage(Context context, String name, int reason) { + int errorMsg; + + switch(reason) { + case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: + errorMsg = R.string.bluetooth_pairing_pin_error_message; + break; + case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: + errorMsg = R.string.bluetooth_pairing_rejected_error_message; + break; + case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: + errorMsg = R.string.bluetooth_pairing_device_down_error_message; + break; + case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: + case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: + case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: + case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: + errorMsg = R.string.bluetooth_pairing_error_message; + break; + default: + Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason); + return; + } + Utils.showError(context, name, errorMsg); + } + } + + private class ClassChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onBtClassChanged(device); + } + } + + private class UuidChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onUuidChanged(device); + } + } + + private class PairingCancelHandler implements Handler { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE"); + return; + } + int errorMsg = R.string.bluetooth_pairing_error_message; + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + Utils.showError(context, cachedDevice.getName(), errorMsg); + } + } + + private class DockEventHandler implements Handler { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + // Remove if unpair device upon undocking + int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); + if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice != null) { + mDeviceManager.onDeviceDisappeared(cachedDevice); + } + } + } + } + } + + boolean readPairedDevices() { + Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices(); + if (bondedDevices == null) { + return false; + } + + boolean deviceAdded = false; + for (BluetoothDevice device : bondedDevices) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); + dispatchDeviceAdded(cachedDevice); + deviceAdded = true; + } + } + + return deviceAdded; + } +} diff --git a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java deleted file mode 100644 index 31747504c..000000000 --- a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.bluetooth; - -import com.android.settings.R; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothInputDevice; -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -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.util.Log; - -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * BluetoothEventRedirector receives broadcasts and callbacks from the Bluetooth - * API and dispatches the event on the UI thread to the right class in the - * Settings. - */ -class BluetoothEventRedirector { - private static final String TAG = "BluetoothEventRedirector"; - - /* package */ final LocalBluetoothManager mManager; - - private final ThreadPoolExecutor mSerialExecutor = new ThreadPoolExecutor( - 0, 1, 1000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); - - private final Handler mHandler = new Handler(); - - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Log.v(TAG, "Received " + intent.getAction()); - - String action = intent.getAction(); - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - mManager.setBluetoothStateInt(state); - } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) { - PendingResult pr = goAsync(); // so loading shared prefs doesn't kill animation - persistDiscoveringTimestamp(pr, true); - } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { - PendingResult pr = goAsync(); // so loading shared prefs doesn't kill animation - persistDiscoveringTimestamp(pr, false); - } else if (action.equals(BluetoothDevice.ACTION_FOUND)) { - short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); - BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); - String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); - // TODO Pick up UUID. They should be available for 2.1 devices. - // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. - mManager.getCachedDeviceManager().onDeviceAppeared(device, rssi, btClass, name); - - } else if (action.equals(BluetoothDevice.ACTION_DISAPPEARED)) { - mManager.getCachedDeviceManager().onDeviceDisappeared(device); - - } else if (action.equals(BluetoothDevice.ACTION_NAME_CHANGED)) { - mManager.getCachedDeviceManager().onDeviceNameUpdated(device); - - } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { - int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, - BluetoothDevice.ERROR); - CachedBluetoothDeviceManager cachedDeviceMgr = mManager.getCachedDeviceManager(); - cachedDeviceMgr.onBondingStateChanged(device, bondState); - if (bondState == BluetoothDevice.BOND_NONE) { - if (device.isBluetoothDock()) { - // After a dock is unpaired, we will forget the - // settings - mManager.removeDockAutoConnectSetting(device.getAddress()); - - // if the device is undocked, remove it from the list as - // well - if (!device.getAddress().equals(getDockedDeviceAddress(context))) { - cachedDeviceMgr.onDeviceDisappeared(device); - } - } - int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, - BluetoothDevice.ERROR); - cachedDeviceMgr.showUnbondMessage(device, reason); - } - - } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); - if (newState == BluetoothProfile.STATE_DISCONNECTED && - oldState == BluetoothProfile.STATE_CONNECTING) { - Log.i(TAG, "Failed to connect BT headset"); - } - - mManager.getCachedDeviceManager().onProfileStateChanged(device, - Profile.HEADSET, newState); - } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { - int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); - if (newState == BluetoothProfile.STATE_DISCONNECTED && - oldState == BluetoothProfile.STATE_CONNECTING) { - Log.i(TAG, "Failed to connect BT A2DP"); - } - - mManager.getCachedDeviceManager().onProfileStateChanged(device, - Profile.A2DP, newState); - } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) { - final int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); - final int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); - if (newState == BluetoothProfile.STATE_DISCONNECTED && - oldState == BluetoothProfile.STATE_CONNECTING) { - Log.i(TAG, "Failed to connect BT HID"); - } - - mManager.getCachedDeviceManager().onProfileStateChanged(device, - Profile.HID, newState); - - } else if (action.equals(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED)) { - final int role = intent.getIntExtra( - BluetoothPan.EXTRA_LOCAL_ROLE, 0); - if (role == BluetoothPan.LOCAL_PANU_ROLE) { - final int newState = intent.getIntExtra( - BluetoothPan.EXTRA_STATE, 0); - final int oldState = intent.getIntExtra( - BluetoothPan.EXTRA_PREVIOUS_STATE, 0); - if (newState == BluetoothPan.STATE_DISCONNECTED && - oldState == BluetoothPan.STATE_CONNECTING) { - Log.i(TAG, "Failed to connect BT PAN"); - } - mManager.getCachedDeviceManager().onProfileStateChanged(device, - Profile.PAN, newState); - } - } else if (action.equals(BluetoothDevice.ACTION_CLASS_CHANGED)) { - mManager.getCachedDeviceManager().onBtClassChanged(device); - - } else if (action.equals(BluetoothDevice.ACTION_UUID)) { - mManager.getCachedDeviceManager().onUuidChanged(device); - - } else if (action.equals(BluetoothDevice.ACTION_PAIRING_CANCEL)) { - int errorMsg = R.string.bluetooth_pairing_error_message; - mManager.showError(device, errorMsg); - - } else if (action.equals(Intent.ACTION_DOCK_EVENT)) { - // Remove if unpair device upon undocking - int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; - int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); - if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { - mManager.getCachedDeviceManager().onDeviceDisappeared(device); - } - } - } - } - }; - - public BluetoothEventRedirector(LocalBluetoothManager localBluetoothManager) { - mManager = localBluetoothManager; - } - - public void registerReceiver() { - IntentFilter filter = new IntentFilter(); - - // Bluetooth on/off broadcasts - filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - - // Discovery broadcasts - filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); - filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); - filter.addAction(BluetoothDevice.ACTION_DISAPPEARED); - filter.addAction(BluetoothDevice.ACTION_FOUND); - filter.addAction(BluetoothDevice.ACTION_NAME_CHANGED); - - // Pairing broadcasts - filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL); - - // Fine-grained state broadcasts - filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothDevice.ACTION_CLASS_CHANGED); - filter.addAction(BluetoothDevice.ACTION_UUID); - - // Dock event broadcasts - filter.addAction(Intent.ACTION_DOCK_EVENT); - - mManager.getContext().registerReceiver(mBroadcastReceiver, filter); - } - - public void stop() { - mManager.getContext().unregisterReceiver(mBroadcastReceiver); - } - - // This can't be called from a broadcast receiver where the filter is set in the Manifest. - /* package */ String getDockedDeviceAddress(Context context) { - // This works only because these broadcast intents are "sticky" - Intent i = context.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - if (i != null) { - int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device != null) { - return device.getAddress(); - } - } - } - return null; - } - - /* package */ void persistDiscoveringTimestamp( - final BroadcastReceiver.PendingResult pr, final boolean newState) { - // Load the shared preferences and edit it on a background - // thread (but serialized!), but then post back to the main - // thread to run the onScanningStateChanged callbacks which - // update the UI... - mSerialExecutor.submit(new Runnable() { - public void run() { - SharedPreferences.Editor editor = mManager.getSharedPreferences().edit(); - editor.putLong( - LocalBluetoothManager.SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP, - System.currentTimeMillis()); - editor.apply(); - mHandler.post(new Runnable() { - public void run() { - mManager.onScanningStateChanged(newState); - pr.finish(); - } - }); - } - }); - } -} diff --git a/src/com/android/settings/bluetooth/BluetoothFindNearby.java b/src/com/android/settings/bluetooth/BluetoothFindNearby.java index f1b876e76..066f4f611 100644 --- a/src/com/android/settings/bluetooth/BluetoothFindNearby.java +++ b/src/com/android/settings/bluetooth/BluetoothFindNearby.java @@ -16,21 +16,18 @@ 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 { +public final class BluetoothFindNearby extends DeviceListPreferenceFragment { - private static final String TAG = "BluetoothFindNearby"; - - void addPreferencesForActivity(Activity activity) { + @Override + void addPreferencesForActivity() { addPreferencesFromResource(R.xml.device_picker); } @@ -38,35 +35,37 @@ public class BluetoothFindNearby extends DeviceListPreferenceFragment { public void onResume() { super.onResume(); if (mSelectedDevice != null) { - CachedBluetoothDevice device = - mLocalManager.getCachedDeviceManager().findDevice(mSelectedDevice); - if (device.getBondState() == BluetoothDevice.BOND_BONDED) { + CachedBluetoothDeviceManager manager = mLocalManager.getCachedDeviceManager(); + CachedBluetoothDevice device = manager.findDevice(mSelectedDevice); + if (device != null && device.getBondState() == BluetoothDevice.BOND_BONDED) { // selected device was paired, so return from this screen finish(); return; } } - mLocalManager.startScanning(true); + mLocalAdapter.startScanning(true); } + @Override void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { - mLocalManager.stopScanning(); + mLocalAdapter.stopScanning(); super.onDevicePreferenceClick(btPreference); } - public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, - int bondState) { + 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) { + @Override + public void onBluetoothStateChanged(int bluetoothState) { super.onBluetoothStateChanged(bluetoothState); if (bluetoothState == BluetoothAdapter.STATE_ON) { - mLocalManager.startScanning(false); + mLocalAdapter.startScanning(false); } } } diff --git a/src/com/android/settings/bluetooth/BluetoothNamePreference.java b/src/com/android/settings/bluetooth/BluetoothNamePreference.java index c99ab4c99..f41689ef7 100644 --- a/src/com/android/settings/bluetooth/BluetoothNamePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothNamePreference.java @@ -22,12 +22,12 @@ import android.app.Dialog; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.preference.EditTextPreference; import android.text.Editable; import android.text.InputFilter; -import android.text.Spanned; import android.text.TextWatcher; import android.util.AttributeSet; import android.widget.Button; @@ -38,13 +38,13 @@ import android.widget.EditText; * Bluetooth name. It asks the user for a name, and persists it via the * Bluetooth API. */ -public class BluetoothNamePreference extends EditTextPreference implements TextWatcher { - private static final String TAG = "BluetoothNamePreference"; +public final class BluetoothNamePreference extends EditTextPreference implements TextWatcher { +// private static final String TAG = "BluetoothNamePreference"; private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248; - private LocalBluetoothManager mLocalManager; + private final LocalBluetoothAdapter mLocalAdapter; - private BroadcastReceiver mReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -61,7 +61,7 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW public BluetoothNamePreference(Context context, AttributeSet attrs) { super(context, attrs); - mLocalManager = LocalBluetoothManager.getInstance(context); + mLocalAdapter = LocalBluetoothManager.getInstance(context).getBluetoothAdapter(); setSummaryToName(); } @@ -97,16 +97,17 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW } private void setSummaryToName() { - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - if (adapter.isEnabled()) { - setSummary(adapter.getName()); + if (mLocalAdapter != null && mLocalAdapter.isEnabled()) { + setSummary(mLocalAdapter.getName()); } } @Override protected boolean persistString(String value) { - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - adapter.setName(value); + // Persist with Bluez instead of shared preferences + if (mLocalAdapter != null) { + mLocalAdapter.setName(value); + } return true; } @@ -116,8 +117,8 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW // The dialog should be created by now EditText et = getEditText(); - if (et != null) { - et.setText(mLocalManager.getBluetoothAdapter().getName()); + if (et != null && mLocalAdapter != null) { + et.setText(mLocalAdapter.getName()); } } @@ -125,7 +126,7 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW public void afterTextChanged(Editable s) { Dialog d = getDialog(); if (d instanceof AlertDialog) { - ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(s.length() > 0); + ((AlertDialog) d).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(s.length() > 0); } } @@ -139,67 +140,4 @@ public class BluetoothNamePreference extends EditTextPreference implements TextW // not used } - /** - * This filter will constrain edits so that the text length is not - * greater than the specified number of bytes using UTF-8 encoding. - * <p>The JNI method used by {@link android.server.BluetoothService} - * to convert UTF-16 to UTF-8 doesn't support surrogate pairs, - * therefore code points outside of the basic multilingual plane - * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters, - * rather than a single 4-byte UTF-8 encoding. Dalvik implements this - * conversion in {@code convertUtf16ToUtf8()} in - * {@code dalvik/vm/UtfString.c}. - * <p>This JNI method is unlikely to change in the future due to - * backwards compatibility requirements. It's also unclear whether - * the installed base of Bluetooth devices would correctly handle the - * encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6. - * However, this filter will still work in scenarios where surrogate - * pairs are encoded as 4 bytes, with the caveat that the maximum - * length will be constrained more conservatively than necessary. - */ - public static class Utf8ByteLengthFilter implements InputFilter { - private int mMaxBytes; - - public Utf8ByteLengthFilter(int maxBytes) { - mMaxBytes = maxBytes; - } - - public CharSequence filter(CharSequence source, int start, int end, - Spanned dest, int dstart, int dend) { - int srcByteCount = 0; - // count UTF-8 bytes in source substring - for (int i = start; i < end; i++) { - char c = source.charAt(i); - srcByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); - } - int destLen = dest.length(); - int destByteCount = 0; - // count UTF-8 bytes in destination excluding replaced section - for (int i = 0; i < destLen; i++) { - if (i < dstart || i >= dend) { - char c = dest.charAt(i); - destByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); - } - } - int keepBytes = mMaxBytes - destByteCount; - if (keepBytes <= 0) { - return ""; - } else if (keepBytes >= srcByteCount) { - return null; // use original dest string - } else { - // find end position of largest sequence that fits in keepBytes - for (int i = start; i < end; i++) { - char c = source.charAt(i); - keepBytes -= (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3); - if (keepBytes < 0) { - return source.subSequence(start, i); - } - } - // If the entire substring fits, we should have returned null - // above, so this line should not be reached. If for some - // reason it is, return null to use the original dest string. - return null; - } - } - } } diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java index 1822e73f8..1b443c497 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingDialog.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingDialog.java @@ -42,33 +42,37 @@ import com.android.settings.R; * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple confirmation * for pairing with a remote Bluetooth device. It is an activity that appears as a dialog. */ -public class BluetoothPairingDialog extends AlertActivity implements DialogInterface.OnClickListener, +public final class BluetoothPairingDialog extends AlertActivity implements DialogInterface.OnClickListener, TextWatcher { private static final String TAG = "BluetoothPairingDialog"; private static final int BLUETOOTH_PIN_MAX_LENGTH = 16; private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6; - private LocalBluetoothManager mLocalManager; private BluetoothDevice mDevice; private int mType; private String mPairingKey; private EditText mPairingView; private Button mOkButton; - private BroadcastReceiver mReceiver = new BroadcastReceiver() { + /** + * Dismiss the dialog if the bond state changes to bonded or none, + * or if pairing was canceled for {@link #mDevice}. + */ + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + String action = intent.getAction(); + if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); if (bondState == BluetoothDevice.BOND_BONDED || bondState == BluetoothDevice.BOND_NONE) { - dismissDialog(); + dismiss(); } - } else if(BluetoothDevice.ACTION_PAIRING_CANCEL.equals(intent.getAction())) { + } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device == null || device.equals(mDevice)) { - dismissDialog(); + dismiss(); } } } @@ -81,48 +85,63 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter Intent intent = getIntent(); if (!intent.getAction().equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { - Log.e(TAG, - "Error: this activity may be started only with intent " + + Log.e(TAG, "Error: this activity may be started only with intent " + BluetoothDevice.ACTION_PAIRING_REQUEST); finish(); + return; } - mLocalManager = LocalBluetoothManager.getInstance(this); + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); + if (manager == null) { + Log.e(TAG, "Error: BluetoothAdapter not supported by system"); + finish(); + return; + } + CachedBluetoothDeviceManager deviceManager = manager.getCachedDeviceManager(); + mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); - if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) { - createUserEntryDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY) { - createUserEntryDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION){ - int passkey = - intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); - if (passkey == BluetoothDevice.ERROR) { - Log.e(TAG, "Invalid ConfirmationPasskey received, not showing any dialog"); - return; - } - mPairingKey = String.format("%06d", passkey); - createConfirmationDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_CONSENT) { - createConsentDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY || - mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { - int pairingKey = - intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); - if (pairingKey == BluetoothDevice.ERROR) { - Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog"); - return; - } - if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { - mPairingKey = String.format("%06d", pairingKey); - } else { - mPairingKey = String.format("%04d", pairingKey); - } - createDisplayPasskeyOrPinDialog(); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) { - createConsentDialog(); - } else { - Log.e(TAG, "Incorrect pairing type received, not showing any dialog"); + + switch (mType) { + case BluetoothDevice.PAIRING_VARIANT_PIN: + case BluetoothDevice.PAIRING_VARIANT_PASSKEY: + createUserEntryDialog(deviceManager); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: + int passkey = + intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); + if (passkey == BluetoothDevice.ERROR) { + Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog"); + return; + } + mPairingKey = String.format("%06d", passkey); + createConfirmationDialog(deviceManager); + break; + + case BluetoothDevice.PAIRING_VARIANT_CONSENT: + case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: + createConsentDialog(deviceManager); + break; + + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: + int pairingKey = + intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); + if (pairingKey == BluetoothDevice.ERROR) { + Log.e(TAG, "Invalid Confirmation Passkey or PIN received, not showing any dialog"); + return; + } + if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { + mPairingKey = String.format("%06d", pairingKey); + } else { + mPairingKey = String.format("%04d", pairingKey); + } + createDisplayPasskeyOrPinDialog(deviceManager); + break; + + default: + Log.e(TAG, "Incorrect pairing type received, not showing any dialog"); } /* @@ -133,67 +152,79 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)); } - private void createUserEntryDialog() { + private void createUserEntryDialog(CachedBluetoothDeviceManager deviceManager) { final AlertController.AlertParams p = mAlertParams; p.mIconId = android.R.drawable.ic_dialog_info; p.mTitle = getString(R.string.bluetooth_pairing_request); - p.mView = createView(); + p.mView = createView(deviceManager); 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 = mAlert.getButton(BUTTON_POSITIVE); mOkButton.setEnabled(false); } - private View createView() { + private View createView(CachedBluetoothDeviceManager deviceManager) { View view = getLayoutInflater().inflate(R.layout.bluetooth_pin_entry, null); - - String name = mLocalManager.getCachedDeviceManager().getName(mDevice); + String name = deviceManager.getName(mDevice); TextView messageView = (TextView) view.findViewById(R.id.message); mPairingView = (EditText) view.findViewById(R.id.text); mPairingView.addTextChangedListener(this); - if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) { - messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name)); - // Maximum of 16 characters in a PIN adb sync - mPairingView.setFilters(new InputFilter[] { - new LengthFilter(BLUETOOTH_PIN_MAX_LENGTH) }); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY){ - messageView.setText(getString(R.string.bluetooth_enter_passkey_msg, name)); - // Maximum of 6 digits for passkey - mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER | - InputType.TYPE_NUMBER_FLAG_SIGNED); - mPairingView.setFilters(new InputFilter[] { - new LengthFilter(BLUETOOTH_PASSKEY_MAX_LENGTH)}); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) { - mPairingView.setVisibility(View.GONE); - messageView.setText(getString(R.string.bluetooth_confirm_passkey_msg, name, - mPairingKey)); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_CONSENT) { - mPairingView.setVisibility(View.GONE); - messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name)); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY || - mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { - mPairingView.setVisibility(View.GONE); - messageView.setText(getString(R.string.bluetooth_display_passkey_pin_msg, name, - mPairingKey)); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) { - mPairingView.setVisibility(View.GONE); - messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name)); - } else { - Log.e(TAG, "Incorrect pairing type received, not creating view"); + switch (mType) { + case BluetoothDevice.PAIRING_VARIANT_PIN: + messageView.setText(getString(R.string.bluetooth_enter_pin_msg, name)); + // Maximum of 16 characters in a PIN adb sync + mPairingView.setFilters(new InputFilter[] { + new LengthFilter(BLUETOOTH_PIN_MAX_LENGTH) }); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY: + messageView.setText(getString(R.string.bluetooth_enter_passkey_msg, name)); + // Maximum of 6 digits for passkey + mPairingView.setInputType(InputType.TYPE_CLASS_NUMBER | + InputType.TYPE_NUMBER_FLAG_SIGNED); + mPairingView.setFilters(new InputFilter[] { + new LengthFilter(BLUETOOTH_PASSKEY_MAX_LENGTH)}); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: + mPairingView.setVisibility(View.GONE); + messageView.setText(getString(R.string.bluetooth_confirm_passkey_msg, name, + mPairingKey)); + break; + + case BluetoothDevice.PAIRING_VARIANT_CONSENT: + mPairingView.setVisibility(View.GONE); + messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name)); + break; + + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: + mPairingView.setVisibility(View.GONE); + messageView.setText(getString(R.string.bluetooth_display_passkey_pin_msg, name, + mPairingKey)); + break; + + case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: + mPairingView.setVisibility(View.GONE); + messageView.setText(getString(R.string.bluetooth_incoming_pairing_msg, name)); + break; + + default: + Log.e(TAG, "Incorrect pairing type received, not creating view"); } return view; } - private void createConfirmationDialog() { + private void createConfirmationDialog(CachedBluetoothDeviceManager deviceManager) { final AlertController.AlertParams p = mAlertParams; p.mIconId = android.R.drawable.ic_dialog_info; p.mTitle = getString(R.string.bluetooth_pairing_request); - p.mView = createView(); + p.mView = createView(deviceManager); p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept); p.mPositiveButtonListener = this; p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline); @@ -201,11 +232,11 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter setupAlert(); } - private void createConsentDialog() { + private void createConsentDialog(CachedBluetoothDeviceManager deviceManager) { final AlertController.AlertParams p = mAlertParams; p.mIconId = android.R.drawable.ic_dialog_info; p.mTitle = getString(R.string.bluetooth_pairing_request); - p.mView = createView(); + p.mView = createView(deviceManager); p.mPositiveButtonText = getString(R.string.bluetooth_pairing_accept); p.mPositiveButtonListener = this; p.mNegativeButtonText = getString(R.string.bluetooth_pairing_decline); @@ -213,11 +244,12 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter setupAlert(); } - private void createDisplayPasskeyOrPinDialog() { + private void createDisplayPasskeyOrPinDialog( + CachedBluetoothDeviceManager deviceManager) { final AlertController.AlertParams p = mAlertParams; p.mIconId = android.R.drawable.ic_dialog_info; p.mTitle = getString(R.string.bluetooth_pairing_request); - p.mView = createView(); + p.mView = createView(deviceManager); p.mNegativeButtonText = getString(android.R.string.cancel); p.mNegativeButtonListener = this; setupAlert(); @@ -244,32 +276,37 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter } } - private void dismissDialog() { - this.dismiss(); - } - private void onPair(String value) { - if (mType == BluetoothDevice.PAIRING_VARIANT_PIN) { - byte[] pinBytes = BluetoothDevice.convertPinToBytes(value); - if (pinBytes == null) { - return; - } - mDevice.setPin(pinBytes); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY) { - int passkey = Integer.parseInt(value); - mDevice.setPasskey(passkey); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) { - mDevice.setPairingConfirmation(true); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_CONSENT) { - mDevice.setPairingConfirmation(true); - } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { - // Do Nothing. - } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { - // Do Nothing - } else if (mType == BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT) { - mDevice.setRemoteOutOfBandData(); - } else { - Log.e(TAG, "Incorrect pairing type received"); + switch (mType) { + case BluetoothDevice.PAIRING_VARIANT_PIN: + byte[] pinBytes = BluetoothDevice.convertPinToBytes(value); + if (pinBytes == null) { + return; + } + mDevice.setPin(pinBytes); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY: + int passkey = Integer.parseInt(value); + mDevice.setPasskey(passkey); + break; + + case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: + case BluetoothDevice.PAIRING_VARIANT_CONSENT: + mDevice.setPairingConfirmation(true); + break; + + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: + case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: + // Do nothing. + break; + + case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: + mDevice.setRemoteOutOfBandData(); + break; + + default: + Log.e(TAG, "Incorrect pairing type received"); } } @@ -279,11 +316,12 @@ public class BluetoothPairingDialog extends AlertActivity implements DialogInter public void onClick(DialogInterface dialog, int which) { switch (which) { - case DialogInterface.BUTTON_POSITIVE: + case BUTTON_POSITIVE: onPair(mPairingView.getText().toString()); break; - case DialogInterface.BUTTON_NEGATIVE: + case BUTTON_NEGATIVE: + default: onCancel(); break; } diff --git a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java index 6037c82a9..de96d7119 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java @@ -35,17 +35,15 @@ import android.os.PowerManager; * confirmation entry dialog. Otherwise it puts a Notification in the status bar, which can * be clicked to bring up the Pairing entry dialog. */ -public class BluetoothPairingRequest extends BroadcastReceiver { +public final class BluetoothPairingRequest extends BroadcastReceiver { - public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth; + private 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(BluetoothDevice.ACTION_PAIRING_REQUEST)) { - - LocalBluetoothManager localManager = LocalBluetoothManager.getInstance(context); - + // convert broadcast intent into activity intent (same action string) BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, @@ -68,39 +66,35 @@ public class BluetoothPairingRequest extends BroadcastReceiver { (PowerManager)context.getSystemService(Context.POWER_SERVICE); String deviceAddress = device != null ? device.getAddress() : null; if (powerManager.isScreenOn() && - localManager.shouldShowDialogInForeground(deviceAddress)) { + LocalBluetoothPreferences.shouldShowDialogInForeground(context, deviceAddress)) { // Since the screen is on and the BT-related activity is in the foreground, // just open the dialog context.startActivity(pairingIntent); - } 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()); + Notification.Builder builder = new Notification.Builder(context) + .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) + .setTicker(res.getString(R.string.bluetooth_notif_ticker)); PendingIntent pending = PendingIntent.getActivity(context, 0, pairingIntent, PendingIntent.FLAG_ONE_SHOT); String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); if (TextUtils.isEmpty(name)) { - name = (device != null ? device.getName() : - context.getString(android.R.string.unknownName)); + name = device != null ? device.getName() : + context.getString(android.R.string.unknownName); } - 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; - notification.defaults |= Notification.DEFAULT_SOUND; + builder.setContentTitle(res.getString(R.string.bluetooth_notif_title)) + .setContentText(res.getString(R.string.bluetooth_notif_message, name)) + .setContentIntent(pending) + .setAutoCancel(true) + .setDefaults(Notification.DEFAULT_SOUND); NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - manager.notify(NOTIFICATION_ID, notification); + manager.notify(NOTIFICATION_ID, builder.getNotification()); } } else if (action.equals(BluetoothDevice.ACTION_PAIRING_CANCEL)) { diff --git a/src/com/android/settings/bluetooth/BluetoothProfilePreference.java b/src/com/android/settings/bluetooth/BluetoothProfilePreference.java index c74012a20..e334867ec 100644 --- a/src/com/android/settings/bluetooth/BluetoothProfilePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothProfilePreference.java @@ -16,9 +16,6 @@ package com.android.settings.bluetooth; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; -import com.android.settings.R; - import android.content.Context; import android.graphics.drawable.Drawable; import android.preference.Preference; @@ -26,22 +23,24 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; +import com.android.settings.R; + /** * BluetoothProfilePreference is the preference type used to display each profile for a * particular bluetooth device. */ -public class BluetoothProfilePreference extends Preference implements OnClickListener { +final class BluetoothProfilePreference extends Preference implements OnClickListener { - private static final String TAG = "BluetoothProfilePreference"; +// private static final String TAG = "BluetoothProfilePreference"; private Drawable mProfileDrawable; private boolean mExpanded; private ImageView mProfileExpandView; - private final Profile mProfile; + private final LocalBluetoothProfile mProfile; private OnClickListener mOnExpandClickListener; - public BluetoothProfilePreference(Context context, Profile profile) { + BluetoothProfilePreference(Context context, LocalBluetoothProfile profile) { super(context); mProfile = profile; @@ -75,14 +74,14 @@ public class BluetoothProfilePreference extends Preference implements OnClickLis btProfile.setImageDrawable(mProfileDrawable); mProfileExpandView = (ImageView) view.findViewById(R.id.profileExpand); - if (mProfile == Profile.PAN) { - mProfileExpandView.setVisibility(View.GONE); - } else { + if (mProfile.isAutoConnectable()) { mProfileExpandView.setOnClickListener(this); mProfileExpandView.setTag(mProfile); mProfileExpandView.setImageResource(mExpanded ? com.android.internal.R.drawable.expander_open_holo_dark : com.android.internal.R.drawable.expander_close_holo_dark); + } else { + mProfileExpandView.setVisibility(View.GONE); } } diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java index 07121e924..c6ba9af56 100644 --- a/src/com/android/settings/bluetooth/BluetoothSettings.java +++ b/src/com/android/settings/bluetooth/BluetoothSettings.java @@ -16,15 +16,13 @@ package com.android.settings.bluetooth; -import android.app.Activity; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; -import android.preference.PreferenceCategory; +import android.preference.PreferenceActivity; import android.preference.PreferenceScreen; -import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -34,9 +32,7 @@ import com.android.settings.R; * BluetoothSettings is the Settings screen for Bluetooth configuration and * connection management. */ -public class BluetoothSettings extends DeviceListPreferenceFragment - implements LocalBluetoothManager.Callback, View.OnClickListener { - +public final class BluetoothSettings extends DeviceListPreferenceFragment { private static final String TAG = "BluetoothSettings"; private static final String KEY_BT_CHECKBOX = "bt_checkbox"; @@ -53,13 +49,20 @@ public class BluetoothSettings extends DeviceListPreferenceFragment private static final String BTOPP_ACTION_OPEN_RECEIVED_FILES = "android.btopp.intent.action.OPEN_RECEIVED_FILES"; - void addPreferencesForActivity(Activity activity) { + /** Initialize the filter to show bonded devices only. */ + public BluetoothSettings() { + super(BluetoothDeviceFilter.BONDED_DEVICE_FILTER); + } + + @Override + void addPreferencesForActivity() { addPreferencesFromResource(R.xml.bluetooth_settings); - mEnabler = new BluetoothEnabler(activity, + mEnabler = new BluetoothEnabler(getActivity(), mLocalAdapter, (CheckBoxPreference) findPreference(KEY_BT_CHECKBOX)); - mDiscoverableEnabler = new BluetoothDiscoverableEnabler(activity, + mDiscoverableEnabler = new BluetoothDiscoverableEnabler(getActivity(), + mLocalAdapter, (CheckBoxPreference) findPreference(KEY_BT_DISCOVERABLE), (ListPreference) findPreference(KEY_BT_DISCOVERABLE_TIMEOUT)); @@ -88,6 +91,27 @@ public class BluetoothSettings extends DeviceListPreferenceFragment mEnabler.pause(); } + 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); + } + } + }; + @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { @@ -105,9 +129,7 @@ public class BluetoothSettings extends DeviceListPreferenceFragment if (bondState == BluetoothDevice.BOND_BONDED) { // add to "Paired devices" list after remote-initiated pairing if (mDevicePreferenceMap.get(cachedDevice) == null) { - if (addDevicePreference(cachedDevice)) { - createDevicePreference(cachedDevice); - } + createDevicePreference(cachedDevice); } } else if (bondState == BluetoothDevice.BOND_NONE) { // remove unpaired device from paired devices list @@ -116,21 +138,11 @@ public class BluetoothSettings extends DeviceListPreferenceFragment } /** - * 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; - } - } - - /** * Add a listener, which enables the advanced settings icon. * @param preference the newly added preference */ + @Override void initDevicePreference(BluetoothDevicePreference preference) { - preference.setOnSettingsClickListener(this); + preference.setOnSettingsClickListener(mListener); } } diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java index 11885ac6c..0bc816c9d 100644 --- a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java +++ b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java @@ -16,30 +16,18 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; - -import android.app.AlertDialog; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; +import android.bluetooth.BluetoothProfile; import android.os.ParcelUuid; import android.os.SystemClock; -import android.preference.Preference; -import android.preference.PreferenceActivity; import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.Map; /** * CachedBluetoothDevice represents a remote Bluetooth device. It contains @@ -47,27 +35,23 @@ import java.util.Map; * functionality that can be performed on the device (connect, pair, disconnect, * etc.). */ -class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { +final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { private static final String TAG = "CachedBluetoothDevice"; - private static final boolean D = LocalBluetoothManager.D; - private static final boolean V = LocalBluetoothManager.V; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Utils.V; + private final LocalBluetoothAdapter mLocalAdapter; + private final LocalBluetoothProfileManager mProfileManager; private final BluetoothDevice mDevice; private String mName; private short mRssi; private BluetoothClass mBtClass; - private Context mContext; - private List<Profile> mProfiles = new ArrayList<Profile>(); + private final List<LocalBluetoothProfile> mProfiles = + new ArrayList<LocalBluetoothProfile>(); private boolean mVisible; - private final LocalBluetoothManager mLocalManager; - - private AlertDialog mDialog = null; - - private List<Callback> mCallbacks = new ArrayList<Callback>(); + private final Collection<Callback> mCallbacks = new ArrayList<Callback>(); /** * When we connect to multiple profiles, we only want to display a single @@ -95,186 +79,62 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { * @param profile Profile to describe * @return Description of the device and profile */ - private String describe(Profile profile) { + private String describe(LocalBluetoothProfile profile) { StringBuilder sb = new StringBuilder(); sb.append("Address:").append(mDevice); if (profile != null) { - sb.append(" Profile:").append(profile.name()); + sb.append(" Profile:").append(profile); } return sb.toString(); } - public void onProfileStateChanged(Profile profile, int newProfileState) { - if (D) { - Log.d(TAG, "onProfileStateChanged: profile " + profile.toString() + + void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { + if (Utils.D) { + Log.d(TAG, "onProfileStateChanged: profile " + profile + " newProfileState " + newProfileState); } - final LocalBluetoothProfileManager pm = - LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); - if (pm == null) return; - int newState = pm.convertState(newProfileState); - - if (newState == SettingsBtStatus.CONNECTION_STATUS_CONNECTED) { + if (newProfileState == BluetoothProfile.STATE_CONNECTED) { if (!mProfiles.contains(profile)) { mProfiles.add(profile); } } } - CachedBluetoothDevice(Context context, BluetoothDevice device) { - mLocalManager = LocalBluetoothManager.getInstance(context); - if (mLocalManager == null) { - throw new IllegalStateException( - "Cannot use CachedBluetoothDevice without Bluetooth hardware"); - } - + CachedBluetoothDevice(LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + mLocalAdapter = adapter; + mProfileManager = profileManager; mDevice = device; - mContext = context; - fillData(); } - public void onClicked() { - int bondState = getBondState(); - - if (isConnected()) { - askDisconnect(); - } else if (bondState == BluetoothDevice.BOND_BONDED) { - connect(true); - } else if (bondState == BluetoothDevice.BOND_NONE) { - pair(); - } - } - - public void disconnect() { - for (Profile profile : mProfiles) { + void disconnect() { + for (LocalBluetoothProfile profile : mProfiles) { disconnect(profile); } } - public void disconnect(Profile profile) { - LocalBluetoothProfileManager profileManager = - LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); - if (profileManager.disconnect(mDevice)) { - if (D) { + void disconnect(LocalBluetoothProfile profile) { + if (profile.disconnect(mDevice)) { + if (Utils.D) { Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); } } } - public void askDisconnect() { - Context context = mLocalManager.getForegroundActivity(); - if (context == null) { - // Cannot ask, since we need an activity context - disconnect(); + void connect(boolean connectAllProfiles) { + if (!ensurePaired()) { 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(); - } - }; - - showDisconnectDialog(context, disconnectListener, message); - } - - public void askDisconnect(final Profile profile) { - Context context = mLocalManager.getForegroundActivity(); - if (context == null) { - // Cannot ask, since we need an activity context - disconnect(profile); - return; - } - - Resources res = context.getResources(); - - String name = getName(); - if (TextUtils.isEmpty(name)) { - name = res.getString(R.string.bluetooth_device); - } - int disconnectMessage; - switch (profile) { - case A2DP: - disconnectMessage = R.string.bluetooth_disconnect_a2dp_profile; - break; - case HEADSET: - disconnectMessage = R.string.bluetooth_disconnect_headset_profile; - break; - case HID: - disconnectMessage = R.string.bluetooth_disconnect_hid_profile; - break; - case PAN: - disconnectMessage = R.string.bluetooth_disconnect_pan_profile; - break; - default: - Log.w(TAG, "askDisconnect: unexpected profile " + profile); - disconnectMessage = R.string.bluetooth_disconnect_blank; - break; - } - String message = res.getString(disconnectMessage, name); - - DialogInterface.OnClickListener disconnectListener = - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - disconnect(profile); - } - }; - - showDisconnectDialog(context, disconnectListener, message); - } - - private void showDisconnectDialog(Context context, - DialogInterface.OnClickListener disconnectListener, - String message) { - if (mDialog == null) { - mDialog = new AlertDialog.Builder(context) - .setPositiveButton(android.R.string.ok, disconnectListener) - .setNegativeButton(android.R.string.cancel, null) - .create(); - } else { - if (mDialog.isShowing()) { - mDialog.dismiss(); - } - // use disconnectListener for the correct profile(s) - CharSequence okText = context.getText(android.R.string.ok); - mDialog.setButton(DialogInterface.BUTTON_POSITIVE, - okText, disconnectListener); - } - mDialog.setTitle(getName()); - mDialog.setMessage(message); - mDialog.show(); - } - - @Override - protected void finalize() throws Throwable { - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - } - - super.finalize(); - } - - public void connect(boolean connectAllProfiles) { - if (!ensurePaired()) return; - mConnectAttempted = SystemClock.elapsedRealtime(); - connectWithoutResettingTimer(connectAllProfiles); } - /*package*/ void onBondingDockConnect() { + void onBondingDockConnect() { // Attempt to connect if UUIDs are available. Otherwise, // we will connect when the ACTION_UUID intent arrives. connect(false); @@ -282,7 +142,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { private void connectWithoutResettingTimer(boolean connectAllProfiles) { // Try to initialize the profiles if they were not. - if (mProfiles.size() == 0) { + if (mProfiles.isEmpty()) { if (!updateProfiles()) { // If UUIDs are not available yet, connect will be happen // upon arrival of the ACTION_UUID intent. @@ -295,98 +155,85 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { mIsConnectingErrorPossible = true; int preferredProfiles = 0; - for (Profile profile : mProfiles) { + for (LocalBluetoothProfile profile : mProfiles) { if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mLocalManager, profile); - if (profileManager.isPreferred(mDevice)) { + if (profile.isPreferred(mDevice)) { ++preferredProfiles; - connectInt(this, profile); + connectInt(profile); } } } if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles); if (preferredProfiles == 0) { - connectAllAutoConnectableProfiles(); + connectAutoConnectableProfiles(); } } - private void connectAllAutoConnectableProfiles() { - if (!ensurePaired()) return; - + private void connectAutoConnectableProfiles() { + if (!ensurePaired()) { + return; + } // Reset the only-show-one-error-dialog tracking variable mIsConnectingErrorPossible = true; - for (Profile profile : mProfiles) { + for (LocalBluetoothProfile profile : mProfiles) { if (profile.isAutoConnectable()) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mLocalManager, profile); - profileManager.setPreferred(mDevice, true); - connectInt(this, profile); + profile.setPreferred(mDevice, true); + connectInt(profile); } } } - public void connect(Profile profile) { + /** + * Connect this device to the specified profile. + * + * @param profile the profile to use with the remote device + */ + void connectProfile(LocalBluetoothProfile profile) { mConnectAttempted = SystemClock.elapsedRealtime(); // Reset the only-show-one-error-dialog tracking variable mIsConnectingErrorPossible = true; - connectInt(this, profile); + connectInt(profile); } - private boolean connectInt(CachedBluetoothDevice cachedDevice, Profile profile) { - if (!cachedDevice.ensurePaired()) return false; - - LocalBluetoothProfileManager profileManager = - LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); - - if (profileManager.connect(cachedDevice.mDevice)) { - if (D) { + private void connectInt(LocalBluetoothProfile profile) { + if (!ensurePaired()) { + return; + } + if (profile.connect(mDevice)) { + if (Utils.D) { Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); } - return true; + return; } - Log.i(TAG, "Failed to connect " + profile.toString() + " to " + cachedDevice.mName); - - return false; - } - - public void showConnectingError() { - if (!mIsConnectingErrorPossible) return; - mIsConnectingErrorPossible = false; - - mLocalManager.showError(mDevice, - R.string.bluetooth_connecting_error_message); + Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName); } private boolean ensurePaired() { if (getBondState() == BluetoothDevice.BOND_NONE) { - pair(); + startPairing(); return false; } else { return true; } } - public void pair() { - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - + boolean startPairing() { // Pairing is unreliable while scanning, so cancel discovery - if (adapter.isDiscovering()) { - adapter.cancelDiscovery(); + if (mLocalAdapter.isDiscovering()) { + mLocalAdapter.cancelDiscovery(); } if (!mDevice.createBond()) { - mLocalManager.showError(mDevice, - R.string.bluetooth_pairing_error_message); - return; + return false; } mConnectAfterPairing = true; // auto-connect after pairing + return true; } - public void unpair() { + void unpair() { disconnect(); int state = getBondState(); @@ -396,14 +243,14 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } if (state != BluetoothDevice.BOND_NONE) { - final BluetoothDevice dev = getDevice(); + final BluetoothDevice dev = mDevice; if (dev != null) { final boolean successful = dev.removeBond(); if (successful) { - if (D) { + if (Utils.D) { Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null)); } - } else if (V) { + } else if (Utils.V) { Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " + describe(null)); } @@ -411,6 +258,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } } + // TODO: do any of these need to run async on a background thread? private void fillData() { fetchName(); fetchBtClass(); @@ -421,15 +269,15 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { dispatchAttributesChanged(); } - public BluetoothDevice getDevice() { + BluetoothDevice getDevice() { return mDevice; } - public String getName() { + String getName() { return mName; } - public void setName(String name) { + void setName(String name) { if (!mName.equals(name)) { if (TextUtils.isEmpty(name)) { // TODO: use friendly name for unknown device (bug 1181856) @@ -442,7 +290,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } } - public void refreshName() { + void refreshName() { fetchName(); dispatchAttributesChanged(); } @@ -452,15 +300,15 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { if (TextUtils.isEmpty(mName)) { mName = mDevice.getAddress(); - if (DEBUG) Log.d(TAG, "Default to address. Device has no name (yet) " + mName); + if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName); } } - public void refresh() { + void refresh() { dispatchAttributesChanged(); } - public boolean isVisible() { + boolean isVisible() { return mVisible; } @@ -471,7 +319,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } } - public int getBondState() { + int getBondState() { return mDevice.getBondState(); } @@ -487,11 +335,10 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { * * @return Whether it is connected. */ - public boolean isConnected() { - for (Profile profile : mProfiles) { - int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile) - .getConnectionStatus(mDevice); - if (SettingsBtStatus.isConnectionStatusConnected(status)) { + boolean isConnected() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = profile.getConnectionStatus(mDevice); + if (status == BluetoothProfile.STATE_CONNECTED) { return true; } } @@ -499,81 +346,21 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { return false; } - public boolean isConnectedProfile(Profile profile) { - int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile) - .getConnectionStatus(mDevice); - if (SettingsBtStatus.isConnectionStatusConnected(status)) { - return true; - } + boolean isConnectedProfile(LocalBluetoothProfile profile) { + int status = profile.getConnectionStatus(mDevice); + return status == BluetoothProfile.STATE_CONNECTED; - return false; } - public boolean isBusy() { - for (Profile profile : mProfiles) { - int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile) - .getConnectionStatus(mDevice); - if (SettingsBtStatus.isConnectionStatusBusy(status)) { + boolean isBusy() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = profile.getConnectionStatus(mDevice); + if (status == BluetoothProfile.STATE_CONNECTING + || status == BluetoothProfile.STATE_DISCONNECTING) { return true; } } - - if (getBondState() == BluetoothDevice.BOND_BONDING) { - return true; - } - - return false; - } - - public int getBtClassDrawable() { - if (mBtClass != null) { - switch (mBtClass.getMajorDeviceClass()) { - case BluetoothClass.Device.Major.COMPUTER: - return R.drawable.ic_bt_laptop; - - case BluetoothClass.Device.Major.PHONE: - return R.drawable.ic_bt_cellphone; - - case BluetoothClass.Device.Major.PERIPHERAL: - return getHidClassDrawable(); - - case BluetoothClass.Device.Major.IMAGING: - return R.drawable.ic_bt_imaging; - } - } else { - Log.w(TAG, "mBtClass is null"); - } - - if (mProfiles.size() > 0) { - 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; - } - } else if (mBtClass != null) { - if (mBtClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { - return R.drawable.ic_bt_headphones_a2dp; - - } - if (mBtClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { - return R.drawable.ic_bt_headset_hfp; - } - } - return 0; - } - - private int getHidClassDrawable() { - switch (mBtClass.getDeviceClass()) { - case BluetoothClass.Device.PERIPHERAL_KEYBOARD: - case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING: - return R.drawable.ic_bt_keyboard_hid; - - case BluetoothClass.Device.PERIPHERAL_POINTING: - return R.drawable.ic_bt_pointing_hid; - - default: - return R.drawable.ic_bt_misc_hid; - } + return getBondState() == BluetoothDevice.BOND_BONDING; } /** @@ -587,11 +374,10 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { ParcelUuid[] uuids = mDevice.getUuids(); if (uuids == null) return false; - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - ParcelUuid[] localUuids = adapter.getUuids(); + ParcelUuid[] localUuids = mLocalAdapter.getUuids(); if (localUuids == null) return false; - LocalBluetoothProfileManager.updateProfiles(uuids, localUuids, mProfiles); + mProfileManager.updateProfiles(uuids, localUuids, mProfiles); if (DEBUG) { Log.e(TAG, "updating profiles for " + mDevice.getName()); @@ -599,8 +385,8 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); Log.v(TAG, "UUID:"); - for (int i = 0; i < uuids.length; i++) { - Log.v(TAG, " " + uuids[i]); + for (ParcelUuid uuid : uuids) { + Log.v(TAG, " " + uuid); } } return true; @@ -610,7 +396,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { * Refreshes the UI for the BT class, including fetching the latest value * for the class. */ - public void refreshBtClass() { + void refreshBtClass() { fetchBtClass(); dispatchAttributesChanged(); } @@ -618,7 +404,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { /** * Refreshes the UI when framework alerts us of a UUID change. */ - public void onUuidChanged() { + void onUuidChanged() { updateProfiles(); if (DEBUG) { @@ -630,7 +416,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { * If a connect was attempted earlier without any UUID, we will do the * connect now. */ - if (mProfiles.size() > 0 + if (!mProfiles.isEmpty() && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock .elapsedRealtime()) { connectWithoutResettingTimer(false); @@ -638,7 +424,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { dispatchAttributesChanged(); } - public void onBondingStateChanged(int bondState) { + void onBondingStateChanged(int bondState) { if (bondState == BluetoothDevice.BOND_NONE) { mProfiles.clear(); mConnectAfterPairing = false; // cancel auto-connect @@ -656,52 +442,25 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { } } - public void setBtClass(BluetoothClass btClass) { + void setBtClass(BluetoothClass btClass) { if (btClass != null && mBtClass != btClass) { mBtClass = btClass; dispatchAttributesChanged(); } } - public int getSummary() { - for (Profile profile : mProfiles) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mLocalManager, profile); - int connectionStatus = profileManager.getConnectionStatus(mDevice); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus) || - connectionStatus == SettingsBtStatus.CONNECTION_STATUS_CONNECTING || - connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING) { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); - } - } - - return SettingsBtStatus.getPairingStatusSummary(getBondState()); + BluetoothClass getBtClass() { + return mBtClass; } - public Map<Profile, Drawable> getProfileIcons() { - Map<Profile, Drawable> drawables = new HashMap<Profile, Drawable>(); - - for (Profile profile : mProfiles) { - int iconResource; - if (profile == Profile.HID && mBtClass != null) { - iconResource = getHidClassDrawable(); - } else { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mLocalManager, profile); - iconResource = profileManager.getDrawableResource(); - } - if (iconResource != 0) { - drawables.put(profile, mContext.getResources().getDrawable(iconResource)); - } - } - - return drawables; + List<LocalBluetoothProfile> getProfiles() { + return Collections.unmodifiableList(mProfiles); } - public List<Profile> getConnectableProfiles() { - ArrayList<Profile> connectableProfiles = new ArrayList<Profile>(); - for (Profile profile : mProfiles) { + List<LocalBluetoothProfile> getConnectableProfiles() { + List<LocalBluetoothProfile> connectableProfiles = + new ArrayList<LocalBluetoothProfile>(); + for (LocalBluetoothProfile profile : mProfiles) { if (profile.isConnectable()) { connectableProfiles.add(profile); } @@ -709,36 +468,13 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { return connectableProfiles; } - public void onClickedAdvancedOptions(SettingsPreferenceFragment fragment) { - // TODO: Verify if there really is a case when there's no foreground - // activity - - // 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_DEVICE, - // mDevice); - // context.startActivity(intent); - Preference pref = new Preference(fragment.getActivity()); - pref.setTitle(getName()); - pref.setFragment(DeviceProfilesSettings.class.getName()); - pref.getExtras().putParcelable(DeviceProfilesSettings.EXTRA_DEVICE, mDevice); - ((PreferenceActivity) fragment.getActivity()).onPreferenceStartFragment(fragment, pref); - } - - public void registerCallback(Callback callback) { + void registerCallback(Callback callback) { synchronized (mCallbacks) { mCallbacks.add(callback); } } - public void unregisterCallback(Callback callback) { + void unregisterCallback(Callback callback) { synchronized (mCallbacks) { mCallbacks.remove(callback); } @@ -760,9 +496,8 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { @Override public boolean equals(Object o) { if ((o == null) || !(o instanceof CachedBluetoothDevice)) { - throw new ClassCastException(); + return false; } - return mDevice.equals(((CachedBluetoothDevice) o).mDevice); } @@ -771,11 +506,12 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { return mDevice.getAddress().hashCode(); } + // This comparison uses non-final fields so the sort order may change + // when device attributes change (such as bonding state). Settings + // will completely refresh the device list when this happens. public int compareTo(CachedBluetoothDevice another) { - int comparison; - // Connected above not connected - comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); + int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); if (comparison != 0) return comparison; // Paired above not paired @@ -792,7 +528,7 @@ class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { if (comparison != 0) return comparison; // Fallback on name - return getName().compareTo(another.getName()); + return mName.compareTo(another.mName); } public interface Callback { diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java index 3ee8bc26d..ab71ece7e 100644 --- a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java +++ b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java @@ -16,122 +16,83 @@ package com.android.settings.bluetooth; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.util.Log; - -import com.android.settings.R; -import com.android.settings.bluetooth.LocalBluetoothManager.Callback; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Set; /** * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. */ -class CachedBluetoothDeviceManager { - private static final String TAG = "CachedBluetoothDeviceManager"; - - final LocalBluetoothManager mLocalManager; - final List<Callback> mCallbacks; - - final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>(); - - public CachedBluetoothDeviceManager(LocalBluetoothManager localManager) { - mLocalManager = localManager; - mCallbacks = localManager.getCallbacks(); - } +final class CachedBluetoothDeviceManager { +// private static final String TAG = "CachedBluetoothDeviceManager"; - private synchronized boolean readPairedDevices() { - BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); - Set<BluetoothDevice> bondedDevices = adapter.getBondedDevices(); - if (bondedDevices == null) return false; - - boolean deviceAdded = false; - for (BluetoothDevice device : bondedDevices) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) { - cachedDevice = new CachedBluetoothDevice(mLocalManager.getContext(), device); - mCachedDevices.add(cachedDevice); - dispatchDeviceAdded(cachedDevice); - deviceAdded = true; - } - } - - return deviceAdded; - } + private final List<CachedBluetoothDevice> mCachedDevices = + new ArrayList<CachedBluetoothDevice>(); - public synchronized List<CachedBluetoothDevice> getCachedDevicesCopy() { + public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() { return new ArrayList<CachedBluetoothDevice>(mCachedDevices); } - void onBluetoothStateChanged(boolean enabled) { - if (enabled) { - readPairedDevices(); - } - } - - public synchronized void onDeviceAppeared(BluetoothDevice device, short rssi, - BluetoothClass btClass, String name) { - boolean deviceAdded = false; - - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) { - cachedDevice = new CachedBluetoothDevice(mLocalManager.getContext(), device); - mCachedDevices.add(cachedDevice); - deviceAdded = true; - } - cachedDevice.setRssi(rssi); - cachedDevice.setBtClass(btClass); - cachedDevice.setName(name); - cachedDevice.setVisible(true); - - if (deviceAdded) { - dispatchDeviceAdded(cachedDevice); - } - } - - public synchronized void onDeviceDisappeared(BluetoothDevice device) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) return; - + public boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) { cachedDevice.setVisible(false); - checkForDeviceRemoval(cachedDevice); + return checkForDeviceRemoval(cachedDevice); } - private void checkForDeviceRemoval(CachedBluetoothDevice cachedDevice) { + private boolean checkForDeviceRemoval( + CachedBluetoothDevice cachedDevice) { if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE && !cachedDevice.isVisible()) { // If device isn't paired, remove it altogether mCachedDevices.remove(cachedDevice); - dispatchDeviceDeleted(cachedDevice); + return true; // dispatch device deleted } + return false; } - public synchronized void onDeviceNameUpdated(BluetoothDevice device) { + public void onDeviceNameUpdated(BluetoothDevice device) { CachedBluetoothDevice cachedDevice = findDevice(device); if (cachedDevice != null) { cachedDevice.refreshName(); } } - public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) { - - for (int i = mCachedDevices.size() - 1; i >= 0; i--) { - CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); - + /** + * Search for existing {@link CachedBluetoothDevice} or return null + * if this device isn't in the cache. Use {@link #addDevice} + * to create and return a new {@link CachedBluetoothDevice} for + * a newly discovered {@link BluetoothDevice}. + * + * @param device the address of the Bluetooth device + * @return the cached device object for this device, or null if it has + * not been previously seen + */ + CachedBluetoothDevice findDevice(BluetoothDevice device) { + for (CachedBluetoothDevice cachedDevice : mCachedDevices) { if (cachedDevice.getDevice().equals(device)) { return cachedDevice; } } - return null; } /** + * Create and return a new {@link CachedBluetoothDevice}. This assumes + * that {@link #findDevice} has already been called and returned null. + * @param device the address of the new Bluetooth device + * @return the newly created CachedBluetoothDevice object + */ + CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + CachedBluetoothDevice newDevice = new CachedBluetoothDevice(adapter, profileManager, + device); + mCachedDevices.add(newDevice); + return newDevice; + } + + /** * Attempts to get the name of a remote device, otherwise returns the address. * * @param device The remote device. @@ -139,122 +100,23 @@ class CachedBluetoothDeviceManager { */ public String getName(BluetoothDevice device) { CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice != null) return cachedDevice.getName(); - - String name = device.getName(); - if (name != null) return name; - - return device.getAddress(); - } - - private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { - synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onDeviceAdded(cachedDevice); - } - } - - // TODO: divider between prev paired/connected and scanned - } - - private void dispatchDeviceDeleted(CachedBluetoothDevice cachedDevice) { - synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onDeviceDeleted(cachedDevice); - } - } - } - - private void dispatchDeviceBondStateChanged( - CachedBluetoothDevice cachedDevice, int bondState) { - synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onDeviceBondStateChanged(cachedDevice, bondState); - } - } - } - - public synchronized void onBondingStateChanged(BluetoothDevice device, int bondState) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) { - if (!readPairedDevices()) { - Log.e(TAG, "Got bonding state changed for " + device + - ", but we have no record of that device."); - return; - } - cachedDevice = findDevice(device); - if (cachedDevice == null) { - Log.e(TAG, "Got bonding state changed for " + device + - ", but device not added in cache."); - return; - } + if (cachedDevice != null) { + return cachedDevice.getName(); } - dispatchDeviceBondStateChanged(cachedDevice, bondState); - cachedDevice.onBondingStateChanged(bondState); - } - - /** - * Called when we have reached the un-bond state. - * - * @param device The remote device. - * @param reason The reason, one of the error reasons from - * BluetoothDevice.UNBOND_REASON_* - */ - public synchronized void showUnbondMessage(BluetoothDevice device, int reason) { - int errorMsg; - - switch(reason) { - case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: - errorMsg = R.string.bluetooth_pairing_pin_error_message; - mLocalManager.showError(device, errorMsg); - break; - case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: - errorMsg = R.string.bluetooth_pairing_rejected_error_message; - mLocalManager.showError(device, errorMsg); - break; - case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: - errorMsg = R.string.bluetooth_pairing_device_down_error_message; - mLocalManager.showError(device, errorMsg); - break; - case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: - case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: - case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: - case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: - errorMsg = R.string.bluetooth_pairing_error_message; - mLocalManager.showError(device, errorMsg); - break; - default: - Log.w(TAG, "showUnbondMessage: Not displaying any message for reason:" + reason); - break; + String name = device.getName(); + if (name != null) { + return name; } - } - public synchronized void onProfileStateChanged(BluetoothDevice device, Profile profile, - int newProfileState) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == null) return; - - cachedDevice.onProfileStateChanged(profile, newProfileState); - cachedDevice.refresh(); - } - - public synchronized void onConnectingError(BluetoothDevice device) { - CachedBluetoothDevice cachedDevice = findDevice(device); - if (cachedDevice == 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. - */ - cachedDevice.showConnectingError(); + return device.getAddress(); } public synchronized void onScanningStateChanged(boolean started) { if (!started) return; // If starting a new scan, clear old visibility + // Iterate in reverse order since devices may be removed. for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); cachedDevice.setVisible(false); diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java index 46fff6e01..a978e2392 100644 --- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java +++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java @@ -16,28 +16,18 @@ 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.Collection; import java.util.WeakHashMap; /** @@ -48,105 +38,96 @@ import java.util.WeakHashMap; * @see DevicePickerFragment * @see BluetoothFindNearby */ -public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFragment - implements LocalBluetoothManager.Callback, View.OnClickListener { +public abstract class DeviceListPreferenceFragment extends + SettingsPreferenceFragment implements BluetoothCallback { private static final String TAG = "DeviceListPreferenceFragment"; - static final String KEY_BT_DEVICE_LIST = "bt_device_list"; - static final String KEY_BT_SCAN = "bt_scan"; + private static final String KEY_BT_DEVICE_LIST = "bt_device_list"; + private static final String KEY_BT_SCAN = "bt_scan"; - int mFilterType; + private BluetoothDeviceFilter.Filter mFilter; - BluetoothDevice mSelectedDevice = null; + BluetoothDevice mSelectedDevice; + LocalBluetoothAdapter mLocalAdapter; LocalBluetoothManager mLocalManager; private PreferenceCategory mDeviceList; - WeakHashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap = + final 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()); - } - } - }; + DeviceListPreferenceFragment() { + mFilter = BluetoothDeviceFilter.ALL_FILTER; + } + + DeviceListPreferenceFragment(BluetoothDeviceFilter.Filter filter) { + mFilter = filter; + } + + final void setFilter(int filterType) { + mFilter = BluetoothDeviceFilter.getFilter(filterType); + } @Override - public void onActivityCreated(Bundle savedInstanceState) { - // We delay calling super.onActivityCreated(). See WifiSettings.java for more info. + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - final Activity activity = getActivity(); - mLocalManager = LocalBluetoothManager.getInstance(activity); + mLocalManager = LocalBluetoothManager.getInstance(getActivity()); if (mLocalManager == null) { - finish(); + Log.e(TAG, "Bluetooth is not supported on this device"); + return; } + mLocalAdapter = mLocalManager.getBluetoothAdapter(); - mFilterType = BluetoothDevicePicker.FILTER_TYPE_ALL; - - if (getPreferenceScreen() != null) getPreferenceScreen().removeAll(); - - addPreferencesForActivity(activity); + addPreferencesForActivity(); mDeviceList = (PreferenceCategory) findPreference(KEY_BT_DEVICE_LIST); - - super.onActivityCreated(savedInstanceState); + if (mDeviceList == null) { + Log.e(TAG, "Could not find device list preference object!"); + } } /** Add preferences from the subclass. */ - abstract void addPreferencesForActivity(Activity activity); + abstract void addPreferencesForActivity(); @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()); + mLocalManager.getEventManager().registerCallback(this); + + updateProgressUi(mLocalAdapter.isDiscovering()); } @Override public void onPause() { super.onPause(); - mLocalManager.stopScanning(); + + mLocalAdapter.stopScanning(); mLocalManager.setForegroundActivity(null); + mLocalManager.getEventManager().unregisterCallback(this); + mDevicePreferenceMap.clear(); mDeviceList.removeAll(); - getActivity().unregisterReceiver(mReceiver); - - mLocalManager.unregisterCallback(this); } void addDevices() { - List<CachedBluetoothDevice> cachedDevices = + Collection<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); + mLocalAdapter.startScanning(true); return true; } @@ -162,7 +143,7 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra } void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { - btPreference.getCachedDevice().onClicked(); + btPreference.onClicked(); } public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { @@ -171,71 +152,11 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra return; } - if (addDevicePreference(cachedDevice)) { + if (mFilter.matches(cachedDevice.getDevice())) { 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); @@ -252,7 +173,8 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra void initDevicePreference(BluetoothDevicePreference preference) { } public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { - BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice); + BluetoothDevicePreference preference = mDevicePreferenceMap.remove( + cachedDevice); if (preference != null) { mDeviceList.removePreference(preference); } @@ -268,15 +190,9 @@ public abstract class DeviceListPreferenceFragment extends SettingsPreferenceFra } } - void onBluetoothStateChanged(int bluetoothState) { + public 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/DevicePickerActivity.java b/src/com/android/settings/bluetooth/DevicePickerActivity.java index c29fafb64..8f6e0dfe3 100644 --- a/src/com/android/settings/bluetooth/DevicePickerActivity.java +++ b/src/com/android/settings/bluetooth/DevicePickerActivity.java @@ -25,7 +25,7 @@ import android.os.Bundle; * Activity for Bluetooth device picker dialog. The device picker logic * is implemented in the {@link BluetoothSettings} fragment. */ -public class DevicePickerActivity extends Activity { +public final class DevicePickerActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java index d3e3d6925..126df022c 100644 --- a/src/com/android/settings/bluetooth/DevicePickerFragment.java +++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java @@ -16,12 +16,11 @@ 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 android.os.Bundle; import com.android.settings.R; @@ -29,36 +28,42 @@ 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"; +public final class DevicePickerFragment extends DeviceListPreferenceFragment { private boolean mNeedAuth; private String mLaunchPackage; private String mLaunchClass; - void addPreferencesForActivity(Activity activity) { - Intent intent = activity.getIntent(); + @Override + void addPreferencesForActivity() { + addPreferencesFromResource(R.xml.device_picker); + + Intent intent = getActivity().getIntent(); mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false); - mFilterType = intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE, - BluetoothDevicePicker.FILTER_TYPE_ALL); + setFilter(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 onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().setTitle(getString(R.string.device_picker)); } @Override public void onResume() { super.onResume(); addDevices(); - mLocalManager.startScanning(true); + mLocalAdapter.startScanning(true); } + @Override void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { - mLocalManager.stopScanning(); - mLocalManager.persistSelectedDeviceInPicker(mSelectedDevice.getAddress()); + mLocalAdapter.stopScanning(); + LocalBluetoothPreferences.persistSelectedDeviceInPicker( + getActivity(), mSelectedDevice.getAddress()); if ((btPreference.getCachedDevice().getBondState() == BluetoothDevice.BOND_BONDED) || !mNeedAuth) { sendDevicePickedIntent(mSelectedDevice); @@ -79,15 +84,16 @@ public class DevicePickerFragment extends DeviceListPreferenceFragment { } } - void onBluetoothStateChanged(int bluetoothState) { + @Override + public void onBluetoothStateChanged(int bluetoothState) { super.onBluetoothStateChanged(bluetoothState); if (bluetoothState == BluetoothAdapter.STATE_ON) { - mLocalManager.startScanning(false); + mLocalAdapter.startScanning(false); } } - void sendDevicePickedIntent(BluetoothDevice device) { + private void sendDevicePickedIntent(BluetoothDevice device) { Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); if (mLaunchPackage != null && mLaunchClass != null) { diff --git a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java index f39eabdc4..307125cb5 100644 --- a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java +++ b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java @@ -16,15 +16,12 @@ package com.android.settings.bluetooth; -import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; - -import android.bluetooth.BluetoothClass; +import android.app.AlertDialog; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothUuid; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.DialogInterface; import android.os.Bundle; -import android.os.ParcelUuid; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.Preference; @@ -34,6 +31,9 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + import java.util.HashMap; /** @@ -41,7 +41,7 @@ import java.util.HashMap; * for a particular device, and allows them to be individually connected * (or disconnected). */ -public class DeviceProfilesSettings extends SettingsPreferenceFragment +public final class DeviceProfilesSettings extends SettingsPreferenceFragment implements CachedBluetoothDevice.Callback, Preference.OnPreferenceChangeListener, View.OnClickListener { private static final String TAG = "DeviceProfilesSettings"; @@ -56,11 +56,15 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment private LocalBluetoothManager mManager; private CachedBluetoothDevice mCachedDevice; + private LocalBluetoothProfileManager mProfileManager; private PreferenceGroup mProfileContainer; private EditTextPreference mDeviceNamePref; - private final HashMap<String,CheckBoxPreference> mAutoConnectPrefs - = new HashMap<String,CheckBoxPreference>(); + + private final HashMap<LocalBluetoothProfile, CheckBoxPreference> mAutoConnectPrefs + = new HashMap<LocalBluetoothProfile, CheckBoxPreference>(); + + private AlertDialog mDisconnectDialog; @Override public void onCreate(Bundle savedInstanceState) { @@ -74,51 +78,54 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment device = args.getParcelable(EXTRA_DEVICE); } + addPreferencesFromResource(R.xml.bluetooth_device_advanced); + getPreferenceScreen().setOrderingAsAdded(false); + mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER); + mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE); + if (device == null) { Log.w(TAG, "Activity started without a remote Bluetooth device"); finish(); - return; + return; // TODO: test this failure path } mManager = LocalBluetoothManager.getInstance(getActivity()); - mCachedDevice = mManager.getCachedDeviceManager().findDevice(device); + CachedBluetoothDeviceManager deviceManager = + mManager.getCachedDeviceManager(); + mProfileManager = mManager.getProfileManager(); + mCachedDevice = deviceManager.findDevice(device); if (mCachedDevice == null) { Log.w(TAG, "Device not found, cannot connect to it"); finish(); - return; + return; // TODO: test this failure path } - addPreferencesFromResource(R.xml.bluetooth_device_advanced); - getPreferenceScreen().setOrderingAsAdded(false); - - mProfileContainer = (PreferenceGroup) findPreference(KEY_PROFILE_CONTAINER); - - mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE); - mDeviceNamePref.setSummary(mCachedDevice.getName()); - mDeviceNamePref.setText(mCachedDevice.getName()); + String deviceName = mCachedDevice.getName(); + mDeviceNamePref.setSummary(deviceName); + mDeviceNamePref.setText(deviceName); mDeviceNamePref.setOnPreferenceChangeListener(this); // Set the title of the screen - findPreference(KEY_TITLE).setTitle(getResources() - .getString(R.string.bluetooth_device_advanced_title, mCachedDevice.getName())); + findPreference(KEY_TITLE).setTitle( + getString(R.string.bluetooth_device_advanced_title, + deviceName)); // Add a preference for each profile addPreferencesForProfiles(); } - private boolean isObjectPushSupported(BluetoothDevice device) { - ParcelUuid[] uuids = device.getUuids(); - BluetoothClass bluetoothClass = device.getBluetoothClass(); - return (uuids != null && BluetoothUuid.containsAnyUuid(uuids, - LocalBluetoothProfileManager.OPP_PROFILE_UUIDS)) || - (bluetoothClass != null && bluetoothClass.doesClassMatch( - BluetoothClass.PROFILE_OPP)); + @Override + public void onDestroy() { + super.onDestroy(); + if (mDisconnectDialog != null) { + mDisconnectDialog.dismiss(); + mDisconnectDialog = null; + } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(EXTRA_DEVICE, mCachedDevice.getDevice()); } @@ -141,7 +148,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment } private void addPreferencesForProfiles() { - for (Profile profile : mCachedDevice.getConnectableProfiles()) { + for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) { Preference pref = createProfilePreference(profile); mProfileContainer.addPreference(pref); } @@ -155,21 +162,18 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment * @return A preference that allows the user to choose whether this profile * will be connected to. */ - private Preference createProfilePreference(Profile profile) { + private Preference createProfilePreference(LocalBluetoothProfile profile) { BluetoothProfilePreference pref = new BluetoothProfilePreference(getActivity(), profile); pref.setKey(profile.toString()); - pref.setTitle(profile.localizedString); + pref.setTitle(profile.getNameResource()); pref.setExpanded(false); pref.setPersistent(false); - pref.setOrder(getProfilePreferenceIndex(profile)); + pref.setOrder(getProfilePreferenceIndex(profile.getOrdinal())); pref.setOnExpandClickListener(this); - LocalBluetoothProfileManager profileManager = - LocalBluetoothProfileManager.getProfileManager(mManager, profile); - int iconResource = profileManager.getDrawableResource(); + int iconResource = profile.getDrawableResource(null); // FIXME: get BT class for this? if (iconResource != 0) { - pref.setProfileDrawable(mManager.getContext() - .getResources().getDrawable(iconResource)); + pref.setProfileDrawable(getResources().getDrawable(iconResource)); } /** @@ -186,7 +190,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { String key = preference.getKey(); if (preference instanceof BluetoothProfilePreference) { - onProfileClicked(Profile.valueOf(key)); + onProfileClicked(mProfileManager.getProfileByName(key)); return true; } else if (key.equals(KEY_UNPAIR)) { unpairDevice(); @@ -194,7 +198,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment return true; } - return false; + return super.onPreferenceTreeClick(screen, preference); } public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -202,10 +206,8 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment mCachedDevice.setName((String) newValue); } else if (preference instanceof CheckBoxPreference) { boolean autoConnect = (Boolean) newValue; - Profile prof = getProfileOf(preference); - LocalBluetoothProfileManager - .getProfileManager(mManager, prof) - .setPreferred(mCachedDevice.getDevice(), + LocalBluetoothProfile prof = getProfileOf(preference); + prof.setPreferred(mCachedDevice.getDevice(), autoConnect); return true; } else { @@ -215,20 +217,44 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment return true; } - private void onProfileClicked(Profile profile) { + private void onProfileClicked(LocalBluetoothProfile profile) { BluetoothDevice device = mCachedDevice.getDevice(); - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mManager, profile); - int status = profileManager.getConnectionStatus(device); + int status = profile.getConnectionStatus(device); boolean isConnected = - SettingsBtStatus.isConnectionStatusConnected(status); + status == BluetoothProfile.STATE_CONNECTED; if (isConnected) { - mCachedDevice.askDisconnect(profile); + askDisconnect(getActivity(), profile); } else { - mCachedDevice.connect(profile); + mCachedDevice.connectProfile(profile); + } + } + + private void askDisconnect(Context context, + final LocalBluetoothProfile profile) { + // local reference for callback + final CachedBluetoothDevice device = mCachedDevice; + String name = device.getName(); + if (TextUtils.isEmpty(name)) { + name = context.getString(R.string.bluetooth_device); + } + int disconnectMessage = profile.getDisconnectResource(); + if (disconnectMessage == 0) { + Log.w(TAG, "askDisconnect: unexpected profile " + profile); + disconnectMessage = R.string.bluetooth_disconnect_blank; } + String message = context.getString(disconnectMessage, name); + + DialogInterface.OnClickListener disconnectListener = + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + device.disconnect(profile); + } + }; + + mDisconnectDialog = Utils.showDisconnectDialog(context, + mDisconnectDialog, disconnectListener, name, message); } public void onDeviceAttributesChanged() { @@ -242,7 +268,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment // transaction.setBreadCrumbTitle(deviceName); // transaction.commit(); - findPreference(KEY_TITLE).setTitle(getResources().getString( + findPreference(KEY_TITLE).setTitle(getString( R.string.bluetooth_device_advanced_title, deviceName)); mDeviceNamePref = (EditTextPreference) findPreference(KEY_RENAME_DEVICE); @@ -253,7 +279,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment } private void refreshProfiles() { - for (Profile profile : mCachedDevice.getConnectableProfiles()) { + for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) { Preference profilePref = findPreference(profile.toString()); if (profilePref == null) { profilePref = createProfilePreference(profile); @@ -264,78 +290,43 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment } } - private void refreshProfilePreference(Preference profilePref, Profile profile) { + private void refreshProfilePreference(Preference profilePref, LocalBluetoothProfile profile) { BluetoothDevice device = mCachedDevice.getDevice(); - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mManager, profile); - - int connectionStatus = profileManager.getConnectionStatus(device); /* * Gray out checkbox while connecting and disconnecting */ profilePref.setEnabled(!mCachedDevice.isBusy()); - profilePref.setSummary(getProfileSummary(profileManager, profile, device, - connectionStatus, isDeviceOnline())); - // TODO: - //profilePref.setChecked(profileManager.isPreferred(device)); + profilePref.setSummary(profile.getSummaryResourceForDevice(device)); } - private Profile getProfileOf(Preference pref) { - if (!(pref instanceof CheckBoxPreference)) return null; + private LocalBluetoothProfile 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 mProfileManager.getProfileByName(pref.getKey()); + } catch (IllegalArgumentException ignored) { return null; } } - private static int getProfileSummary(LocalBluetoothProfileManager profileManager, - Profile profile, BluetoothDevice device, int connectionStatus, boolean onlineMode) { - if (!onlineMode || connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED) { - return getProfileSummaryForSettingPreference(profile); - } else { - return profileManager.getSummary(device); - } - } - - /** - * 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; - case HID: - return R.string.bluetooth_hid_profile_summary_use_for; - case PAN: - return R.string.bluetooth_pan_profile_summary_use_for; - default: - return 0; - } - } - public void onClick(View v) { - if (v.getTag() instanceof Profile) { - Profile prof = (Profile) v.getTag(); - CheckBoxPreference autoConnectPref = mAutoConnectPrefs.get(prof.toString()); + if (v.getTag() instanceof LocalBluetoothProfile) { + LocalBluetoothProfile prof = (LocalBluetoothProfile) v.getTag(); + CheckBoxPreference autoConnectPref = mAutoConnectPrefs.get(prof); if (autoConnectPref == null) { autoConnectPref = new CheckBoxPreference(getActivity()); autoConnectPref.setLayoutResource(com.android.internal.R.layout.preference_child); autoConnectPref.setKey(prof.toString()); autoConnectPref.setTitle(R.string.bluetooth_auto_connect); - autoConnectPref.setOrder(getProfilePreferenceIndex(prof) + 1); + autoConnectPref.setOrder(getProfilePreferenceIndex(prof.getOrdinal()) + 1); autoConnectPref.setChecked(getAutoConnect(prof)); autoConnectPref.setOnPreferenceChangeListener(this); - mAutoConnectPrefs.put(prof.name(), autoConnectPref); + mAutoConnectPrefs.put(prof, autoConnectPref); } BluetoothProfilePreference profilePref = (BluetoothProfilePreference) findPreference(prof.toString()); @@ -349,19 +340,14 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment } } - private int getProfilePreferenceIndex(Profile prof) { - return mProfileContainer.getOrder() + prof.ordinal() * 10; + private int getProfilePreferenceIndex(int profIndex) { + return mProfileContainer.getOrder() + profIndex * 10; } private void unpairDevice() { mCachedDevice.unpair(); } - private boolean isDeviceOnline() { - // TODO: Verify - return mCachedDevice.isConnected() || mCachedDevice.isBusy(); - } - private void setIncomingFileTransfersAllowed(boolean allow) { // TODO: make an IPC call into BluetoothOpp to update Log.d(TAG, "Set allow incoming = " + allow); @@ -372,8 +358,7 @@ public class DeviceProfilesSettings extends SettingsPreferenceFragment return true; } - private boolean getAutoConnect(Profile prof) { - return LocalBluetoothProfileManager.getProfileManager(mManager, prof) - .isPreferred(mCachedDevice.getDevice()); + private boolean getAutoConnect(LocalBluetoothProfile prof) { + return prof.isPreferred(mCachedDevice.getDevice()); } } diff --git a/src/com/android/settings/bluetooth/DockEventReceiver.java b/src/com/android/settings/bluetooth/DockEventReceiver.java index 041099813..6ecbef63a 100644 --- a/src/com/android/settings/bluetooth/DockEventReceiver.java +++ b/src/com/android/settings/bluetooth/DockEventReceiver.java @@ -28,7 +28,7 @@ import android.content.Intent; import android.os.PowerManager; import android.util.Log; -public class DockEventReceiver extends BroadcastReceiver { +public final class DockEventReceiver extends BroadcastReceiver { private static final boolean DEBUG = DockService.DEBUG; @@ -39,11 +39,9 @@ public class DockEventReceiver extends BroadcastReceiver { private static final int EXTRA_INVALID = -1234; - private static final Object mStartingServiceSync = new Object(); + private static final Object sStartingServiceSync = new Object(); - private static final long WAKELOCK_TIMEOUT = 5000; - - private static PowerManager.WakeLock mStartingService; + private static PowerManager.WakeLock sStartingService; @Override public void onReceive(Context context, Intent intent) { @@ -75,7 +73,7 @@ public class DockEventReceiver extends BroadcastReceiver { beginStartingService(context, i); break; default: - if (DEBUG) Log.e(TAG, "Unknown state"); + Log.e(TAG, "Unknown state: " + state); break; } } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction()) || @@ -118,15 +116,15 @@ public class DockEventReceiver extends BroadcastReceiver { * Start the service to process the current event notifications, acquiring * the wake lock before returning to ensure that the service will run. */ - public static void beginStartingService(Context context, Intent intent) { - synchronized (mStartingServiceSync) { - if (mStartingService == null) { + private static void beginStartingService(Context context, Intent intent) { + synchronized (sStartingServiceSync) { + if (sStartingService == null) { PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + sStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "StartingDockService"); } - mStartingService.acquire(WAKELOCK_TIMEOUT); + sStartingService.acquire(); if (context.startService(intent) == null) { Log.e(TAG, "Can't start DockService"); @@ -139,10 +137,13 @@ public class DockEventReceiver extends BroadcastReceiver { * releasing the wake lock if the service is now stopping. */ public static void finishStartingService(Service service, int startId) { - synchronized (mStartingServiceSync) { - if (mStartingService != null) { - if (DEBUG) Log.d(TAG, "stopSelf id = "+ startId); - service.stopSelfResult(startId); + synchronized (sStartingServiceSync) { + if (sStartingService != null) { + if (DEBUG) Log.d(TAG, "stopSelf id = " + startId); + if (service.stopSelfResult(startId)) { + Log.d(TAG, "finishStartingService: stopping service"); + sStartingService.release(); + } } } } diff --git a/src/com/android/settings/bluetooth/DockService.java b/src/com/android/settings/bluetooth/DockService.java index 810465238..b45770658 100644 --- a/src/com/android/settings/bluetooth/DockService.java +++ b/src/com/android/settings/bluetooth/DockService.java @@ -17,7 +17,6 @@ package com.android.settings.bluetooth; import com.android.settings.R; -import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; import com.android.settings.bluetooth.LocalBluetoothProfileManager.ServiceListener; import android.app.AlertDialog; @@ -27,7 +26,7 @@ import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; -import android.content.Context; +import android.bluetooth.BluetoothProfile; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; @@ -44,12 +43,11 @@ import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; +import java.util.Collection; import java.util.List; import java.util.Set; -public class DockService extends Service implements AlertDialog.OnMultiChoiceClickListener, - DialogInterface.OnClickListener, DialogInterface.OnDismissListener, - CompoundButton.OnCheckedChangeListener, ServiceListener { +public final class DockService extends Service implements ServiceListener { private static final String TAG = "DockService"; @@ -82,14 +80,11 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli private static final String SHARED_PREFERENCES_NAME = "dock_settings"; - private static final String SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED = - "disable_bt_when_undock"; + private static final String KEY_DISABLE_BT_WHEN_UNDOCKED = "disable_bt_when_undock"; - private static final String SHARED_PREFERENCES_KEY_DISABLE_BT = - "disable_bt"; + private static final String KEY_DISABLE_BT = "disable_bt"; - private static final String SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT = - "connect_retry_count"; + private static final String KEY_CONNECT_RETRY_COUNT = "connect_retry_count"; /* * If disconnected unexpectedly, reconnect up to 6 times. Each profile counts @@ -103,8 +98,9 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private Runnable mRunnable; - private DockService mContext; - private LocalBluetoothManager mBtManager; + private LocalBluetoothAdapter mLocalAdapter; + private CachedBluetoothDeviceManager mDeviceManager; + private LocalBluetoothProfileManager mProfileManager; // Normally set after getting a docked event and unset when the connection // is severed. One exception is that mDevice could be null if the service @@ -113,7 +109,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli // Created and used for the duration of the dialog private AlertDialog mDialog; - private Profile[] mProfiles; + private LocalBluetoothProfile[] mProfiles; private boolean[] mCheckedItems; private int mStartIdAssociatedWithDialog; @@ -127,8 +123,19 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli public void onCreate() { if (DEBUG) Log.d(TAG, "onCreate"); - mBtManager = LocalBluetoothManager.getInstance(this); - mContext = this; + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); + if (manager == null) { + Log.e(TAG, "Can't get LocalBluetoothManager: exiting"); + return; + } + + mLocalAdapter = manager.getBluetoothAdapter(); + mDeviceManager = manager.getCachedDeviceManager(); + mProfileManager = manager.getProfileManager(); + if (mProfileManager == null) { + Log.e(TAG, "Can't get LocalBluetoothProfileManager: exiting"); + return; + } HandlerThread thread = new HandlerThread("DockService"); thread.start(); @@ -141,12 +148,22 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli public void onDestroy() { if (DEBUG) Log.d(TAG, "onDestroy"); mRunnable = null; - LocalBluetoothProfileManager.removeServiceListener(this); if (mDialog != null) { mDialog.dismiss(); mDialog = null; } - mServiceLooper.quit(); + if (mProfileManager != null) { + mProfileManager.removeServiceListener(this); + } + if (mServiceLooper != null) { + mServiceLooper.quit(); + } + + mLocalAdapter = null; + mDeviceManager = null; + mProfileManager = null; + mServiceLooper = null; + mServiceHandler = null; } @Override @@ -155,9 +172,13 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return null; } + private SharedPreferences getPrefs() { + return getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE); + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (DEBUG) Log.d(TAG, "onStartCommand startId:" + startId + " flags: " + flags); + if (DEBUG) Log.d(TAG, "onStartCommand startId: " + startId + " flags: " + flags); if (intent == null) { // Nothing to process, stop. @@ -178,24 +199,24 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli * This assumes that the intent sender has checked that this is a dock * and that the intent is for a disconnect */ + final SharedPreferences prefs = getPrefs(); if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { BluetoothDevice disconnectedDevice = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - - int retryCount = getSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, 0); + int retryCount = prefs.getInt(KEY_CONNECT_RETRY_COUNT, 0); if (retryCount < MAX_CONNECT_RETRY) { - setSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, retryCount + 1); - handleUnexpectedDisconnect(disconnectedDevice, Profile.HEADSET, startId); + prefs.edit().putInt(KEY_CONNECT_RETRY_COUNT, retryCount + 1).apply(); + handleUnexpectedDisconnect(disconnectedDevice, mProfileManager.getHeadsetProfile(), startId); } return START_NOT_STICKY; } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { BluetoothDevice disconnectedDevice = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - int retryCount = getSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, 0); + int retryCount = prefs.getInt(KEY_CONNECT_RETRY_COUNT, 0); if (retryCount < MAX_CONNECT_RETRY) { - setSettingInt(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT, retryCount + 1); - handleUnexpectedDisconnect(disconnectedDevice, Profile.A2DP, startId); + prefs.edit().putInt(KEY_CONNECT_RETRY_COUNT, retryCount + 1).apply(); + handleUnexpectedDisconnect(disconnectedDevice, mProfileManager.getA2dpProfile(), startId); } return START_NOT_STICKY; } @@ -209,7 +230,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } if (msg.what == MSG_TYPE_DOCKED) { - removeSetting(SHARED_PREFERENCES_KEY_CONNECT_RETRY_COUNT); + prefs.edit().remove(KEY_CONNECT_RETRY_COUNT).apply(); } msg.arg2 = startId; @@ -219,7 +240,7 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { + private ServiceHandler(Looper looper) { super(looper); } @@ -234,7 +255,6 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli int msgType = msg.what; final int state = msg.arg1; final int startId = msg.arg2; - boolean deferFinishCall = false; BluetoothDevice device = null; if (msg.obj != null) { device = (BluetoothDevice) msg.obj; @@ -243,102 +263,27 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli if(DEBUG) Log.d(TAG, "processMessage: " + msgType + " state: " + state + " device = " + (device == null ? "null" : device.toString())); + boolean deferFinishCall = false; + switch (msgType) { case MSG_TYPE_SHOW_UI: - if (mDialog != null) { - // Shouldn't normally happen - mDialog.dismiss(); - mDialog = null; - } - mDevice = device; - createDialog(mContext, mDevice, state, startId); + createDialog(device, state, startId); break; case MSG_TYPE_DOCKED: - if (DEBUG) { - // TODO figure out why hasMsg always returns false if device - // is supplied - Log.d(TAG, "1 Has undock perm msg = " - + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, mDevice)); - Log.d(TAG, "2 Has undock perm msg = " - + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, device)); - } - - mServiceHandler.removeMessages(MSG_TYPE_UNDOCKED_PERMANENT); - mServiceHandler.removeMessages(MSG_TYPE_DISABLE_BT); - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT); - - if (!device.equals(mDevice)) { - if (mDevice != null) { - // Not expected. Cleanup/undock existing - handleUndocked(mContext, mBtManager, mDevice); - } - - mDevice = device; - - // Register first in case LocalBluetoothProfileManager - // becomes ready after isManagerReady is called and it - // would be too late to register a service listener. - LocalBluetoothProfileManager.addServiceListener(this); - if (LocalBluetoothProfileManager.isManagerReady()) { - handleDocked(device, state, startId); - // Not needed after all - LocalBluetoothProfileManager.removeServiceListener(this); - } else { - final BluetoothDevice d = device; - mRunnable = new Runnable() { - public void run() { - handleDocked(d, state, startId); - } - }; - deferFinishCall = true; - } - } + deferFinishCall = msgTypeDocked(device, state, startId); break; case MSG_TYPE_UNDOCKED_PERMANENT: - // Grace period passed. Disconnect. - handleUndocked(mContext, mBtManager, device); - - if (DEBUG) { - Log.d(TAG, "DISABLE_BT_WHEN_UNDOCKED = " - + getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)); - } - - if (getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)) { - // BT was disabled when we first docked - if (!hasOtherConnectedDevices(device)) { - if(DEBUG) Log.d(TAG, "QUEUED BT DISABLE"); - // Queue a delayed msg to disable BT - Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_DISABLE_BT, 0, - startId, null); - mServiceHandler.sendMessageDelayed(newMsg, DISABLE_BT_GRACE_PERIOD); - deferFinishCall = true; - } else { - // Don't disable BT if something is connected - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED); - } - } + deferFinishCall = msgTypeUndockedPermanent(device, startId); break; case MSG_TYPE_UNDOCKED_TEMPORARY: - // Undocked event received. Queue a delayed msg to sever connection - Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_UNDOCKED_PERMANENT, state, - startId, device); - mServiceHandler.sendMessageDelayed(newMsg, UNDOCKED_GRACE_PERIOD); + msgTypeUndockedTemporary(device, state, startId); break; case MSG_TYPE_DISABLE_BT: - if(DEBUG) Log.d(TAG, "BT DISABLE"); - if (mBtManager.getBluetoothAdapter().disable()) { - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED); - } else { - // disable() returned an error. Persist a flag to disable BT later - setSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT, true); - mPendingTurnOffStartId = startId; - deferFinishCall = true; - if(DEBUG) Log.d(TAG, "disable failed. try again later " + startId); - } + deferFinishCall = msgTypeDisableBluetooth(startId); break; } @@ -346,24 +291,127 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli && !deferFinishCall) { // NOTE: We MUST not call stopSelf() directly, since we need to // make sure the wake lock acquired by the Receiver is released. - DockEventReceiver.finishStartingService(DockService.this, startId); + DockEventReceiver.finishStartingService(this, startId); } } - public synchronized boolean hasOtherConnectedDevices(BluetoothDevice dock) { - List<CachedBluetoothDevice> cachedDevices = mBtManager.getCachedDeviceManager() - .getCachedDevicesCopy(); - Set<BluetoothDevice> btDevices = mBtManager.getBluetoothAdapter().getBondedDevices(); - if (btDevices == null || cachedDevices == null || btDevices.size() == 0) { + private boolean msgTypeDisableBluetooth(int startId) { + if (DEBUG) { + Log.d(TAG, "BT DISABLE"); + } + final SharedPreferences prefs = getPrefs(); + if (mLocalAdapter.disable()) { + prefs.edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); return false; + } else { + // disable() returned an error. Persist a flag to disable BT later + prefs.edit().putBoolean(KEY_DISABLE_BT, true).apply(); + mPendingTurnOffStartId = startId; + if(DEBUG) { + Log.d(TAG, "disable failed. try again later " + startId); + } + return true; } - if(DEBUG) Log.d(TAG, "btDevices = " + btDevices.size()); - if(DEBUG) Log.d(TAG, "cachedDevices = " + cachedDevices.size()); + } - for (CachedBluetoothDevice device : cachedDevices) { - BluetoothDevice btDevice = device.getDevice(); - if (!btDevice.equals(dock) && btDevices.contains(btDevice) && device.isConnected()) { - if(DEBUG) Log.d(TAG, "connected device = " + device.getName()); + private void msgTypeUndockedTemporary(BluetoothDevice device, int state, + int startId) { + // Undocked event received. Queue a delayed msg to sever connection + Message newMsg = mServiceHandler.obtainMessage(MSG_TYPE_UNDOCKED_PERMANENT, state, + startId, device); + mServiceHandler.sendMessageDelayed(newMsg, UNDOCKED_GRACE_PERIOD); + } + + private boolean msgTypeUndockedPermanent(BluetoothDevice device, int startId) { + // Grace period passed. Disconnect. + handleUndocked(device); + final SharedPreferences prefs = getPrefs(); + + if (DEBUG) { + Log.d(TAG, "DISABLE_BT_WHEN_UNDOCKED = " + + prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)); + } + + if (prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)) { + if (hasOtherConnectedDevices(device)) { + // Don't disable BT if something is connected + prefs.edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); + } else { + // BT was disabled when we first docked + if (DEBUG) { + Log.d(TAG, "QUEUED BT DISABLE"); + } + // Queue a delayed msg to disable BT + Message newMsg = mServiceHandler.obtainMessage( + MSG_TYPE_DISABLE_BT, 0, startId, null); + mServiceHandler.sendMessageDelayed(newMsg, + DISABLE_BT_GRACE_PERIOD); + return true; + } + } + return false; + } + + private boolean msgTypeDocked(BluetoothDevice device, final int state, + final int startId) { + if (DEBUG) { + // TODO figure out why hasMsg always returns false if device + // is supplied + Log.d(TAG, "1 Has undock perm msg = " + + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, mDevice)); + Log.d(TAG, "2 Has undock perm msg = " + + mServiceHandler.hasMessages(MSG_TYPE_UNDOCKED_PERMANENT, device)); + } + + mServiceHandler.removeMessages(MSG_TYPE_UNDOCKED_PERMANENT); + mServiceHandler.removeMessages(MSG_TYPE_DISABLE_BT); + getPrefs().edit().remove(KEY_DISABLE_BT).apply(); + + if (device != null && !device.equals(mDevice)) { + if (mDevice != null) { + // Not expected. Cleanup/undock existing + handleUndocked(mDevice); + } + + mDevice = device; + + // Register first in case LocalBluetoothProfileManager + // becomes ready after isManagerReady is called and it + // would be too late to register a service listener. + mProfileManager.addServiceListener(this); + if (mProfileManager.isManagerReady()) { + handleDocked(device, state, startId); + // Not needed after all + mProfileManager.removeServiceListener(this); + } else { + final BluetoothDevice d = device; + mRunnable = new Runnable() { + public void run() { + handleDocked(d, state, startId); // FIXME: WTF runnable here? + } + }; + return true; + } + } + return false; + } + + synchronized boolean hasOtherConnectedDevices(BluetoothDevice dock) { + Collection<CachedBluetoothDevice> cachedDevices = mDeviceManager.getCachedDevicesCopy(); + Set<BluetoothDevice> btDevices = mLocalAdapter.getBondedDevices(); + if (btDevices == null || cachedDevices == null || btDevices.isEmpty()) { + return false; + } + if(DEBUG) { + Log.d(TAG, "btDevices = " + btDevices.size()); + Log.d(TAG, "cachedDeviceUIs = " + cachedDevices.size()); + } + + for (CachedBluetoothDevice deviceUI : cachedDevices) { + BluetoothDevice btDevice = deviceUI.getDevice(); + if (!btDevice.equals(dock) && btDevices.contains(btDevice) && deviceUI + .isConnected()) { + if(DEBUG) Log.d(TAG, "connected deviceUI = " + deviceUI.getName()); return true; } } @@ -404,96 +452,128 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return mServiceHandler.obtainMessage(msgType, state, 0, device); } - private boolean createDialog(DockService service, BluetoothDevice device, int state, - int startId) { + private void createDialog(BluetoothDevice device, + int state, int startId) { + if (mDialog != null) { + // Shouldn't normally happen + mDialog.dismiss(); + mDialog = null; + } + mDevice = device; switch (state) { case Intent.EXTRA_DOCK_STATE_CAR: case Intent.EXTRA_DOCK_STATE_DESK: break; default: - return false; + return; } startForeground(0, new Notification()); // Device in a new dock. - boolean firstTime = !mBtManager.hasDockAutoConnectSetting(device.getAddress()); + boolean firstTime = !LocalBluetoothPreferences.hasDockAutoConnectSetting(this, device.getAddress()); - CharSequence[] items = initBtSettings(service, device, state, firstTime); + CharSequence[] items = initBtSettings(device, state, firstTime); - final AlertDialog.Builder ab = new AlertDialog.Builder(service); - ab.setTitle(service.getString(R.string.bluetooth_dock_settings_title)); + final AlertDialog.Builder ab = new AlertDialog.Builder(this); + ab.setTitle(getString(R.string.bluetooth_dock_settings_title)); // Profiles - ab.setMultiChoiceItems(items, mCheckedItems, service); + ab.setMultiChoiceItems(items, mCheckedItems, mMultiClickListener); // Remember this settings - LayoutInflater inflater = (LayoutInflater) service - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - float pixelScaleFactor = service.getResources().getDisplayMetrics().density; + LayoutInflater inflater = (LayoutInflater) + getSystemService(LAYOUT_INFLATER_SERVICE); + float pixelScaleFactor = getResources().getDisplayMetrics().density; View view = inflater.inflate(R.layout.remember_dock_setting, null); CheckBox rememberCheckbox = (CheckBox) view.findViewById(R.id.remember); // check "Remember setting" by default if no value was saved - boolean checked = firstTime || mBtManager.getDockAutoConnectSetting(device.getAddress()); + boolean checked = firstTime || LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress()); rememberCheckbox.setChecked(checked); - rememberCheckbox.setOnCheckedChangeListener(this); + rememberCheckbox.setOnCheckedChangeListener(mCheckedChangeListener); int viewSpacingLeft = (int) (14 * pixelScaleFactor); int viewSpacingRight = (int) (14 * pixelScaleFactor); ab.setView(view, viewSpacingLeft, 0 /* top */, viewSpacingRight, 0 /* bottom */); if (DEBUG) { Log.d(TAG, "Auto connect = " - + mBtManager.getDockAutoConnectSetting(device.getAddress())); + + LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress())); } // Ok Button - ab.setPositiveButton(service.getString(android.R.string.ok), service); + ab.setPositiveButton(getString(android.R.string.ok), mClickListener); mStartIdAssociatedWithDialog = startId; mDialog = ab.create(); mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - mDialog.setOnDismissListener(service); + mDialog.setOnDismissListener(mDismissListener); mDialog.show(); - return true; } // Called when the individual bt profiles are clicked. - public void onClick(DialogInterface dialog, int which, boolean isChecked) { - if (DEBUG) Log.d(TAG, "Item " + which + " changed to " + isChecked); - mCheckedItems[which] = isChecked; - } + private final DialogInterface.OnMultiChoiceClickListener mMultiClickListener = + new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + if (DEBUG) { + Log.d(TAG, "Item " + which + " changed to " + isChecked); + } + mCheckedItems[which] = isChecked; + } + }; + // Called when the "Remember" Checkbox is clicked - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (DEBUG) Log.d(TAG, "onCheckedChanged: Remember Settings = " + isChecked); - if (mDevice != null) { - mBtManager.saveDockAutoConnectSetting(mDevice.getAddress(), isChecked); - } - } + private final CompoundButton.OnCheckedChangeListener mCheckedChangeListener = + new CompoundButton.OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (DEBUG) { + Log.d(TAG, "onCheckedChanged: Remember Settings = " + isChecked); + } + if (mDevice != null) { + LocalBluetoothPreferences.saveDockAutoConnectSetting( + DockService.this, mDevice.getAddress(), isChecked); + } + } + }; + // Called when the dialog is dismissed - public void onDismiss(DialogInterface dialog) { - // NOTE: We MUST not call stopSelf() directly, since we need to - // make sure the wake lock acquired by the Receiver is released. - if (mPendingDevice == null) { - DockEventReceiver.finishStartingService(mContext, mStartIdAssociatedWithDialog); - } - mContext.stopForeground(true); - } + private final DialogInterface.OnDismissListener mDismissListener = + new DialogInterface.OnDismissListener() { + public void onDismiss(DialogInterface dialog) { + // NOTE: We MUST not call stopSelf() directly, since we need to + // make sure the wake lock acquired by the Receiver is released. + if (mPendingDevice == null) { + DockEventReceiver.finishStartingService( + DockService.this, mStartIdAssociatedWithDialog); + } + stopForeground(true); + } + }; // Called when clicked on the OK button - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE && mDevice != null) { - if (!mBtManager.hasDockAutoConnectSetting(mDevice.getAddress())) { - mBtManager.saveDockAutoConnectSetting(mDevice.getAddress(), true); - } + private final DialogInterface.OnClickListener mClickListener = + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_POSITIVE + && mDevice != null) { + if (!LocalBluetoothPreferences + .hasDockAutoConnectSetting( + DockService.this, + mDevice.getAddress())) { + LocalBluetoothPreferences + .saveDockAutoConnectSetting( + DockService.this, + mDevice.getAddress(), true); + } - applyBtSettings(mDevice, mStartIdAssociatedWithDialog); - } - } + applyBtSettings(mDevice, mStartIdAssociatedWithDialog); + } + } + }; - private CharSequence[] initBtSettings(DockService service, BluetoothDevice device, int state, - boolean firstTime) { + private CharSequence[] initBtSettings(BluetoothDevice device, + int state, boolean firstTime) { // TODO Avoid hardcoding dock and profiles. Read from system properties int numOfProfiles; switch (state) { @@ -507,96 +587,54 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli return null; } - mProfiles = new Profile[numOfProfiles]; + mProfiles = new LocalBluetoothProfile[numOfProfiles]; mCheckedItems = new boolean[numOfProfiles]; CharSequence[] items = new CharSequence[numOfProfiles]; + // FIXME: convert switch to something else switch (state) { case Intent.EXTRA_DOCK_STATE_CAR: - items[0] = service.getString(R.string.bluetooth_dock_settings_headset); - items[1] = service.getString(R.string.bluetooth_dock_settings_a2dp); - mProfiles[0] = Profile.HEADSET; - mProfiles[1] = Profile.A2DP; + items[0] = getString(R.string.bluetooth_dock_settings_headset); + items[1] = getString(R.string.bluetooth_dock_settings_a2dp); + mProfiles[0] = mProfileManager.getHeadsetProfile(); + mProfiles[1] = mProfileManager.getA2dpProfile(); if (firstTime) { // Enable by default for car dock mCheckedItems[0] = true; mCheckedItems[1] = true; } else { - mCheckedItems[0] = LocalBluetoothProfileManager.getProfileManager(mBtManager, - Profile.HEADSET).isPreferred(device); - mCheckedItems[1] = LocalBluetoothProfileManager.getProfileManager(mBtManager, - Profile.A2DP).isPreferred(device); + mCheckedItems[0] = mProfiles[0].isPreferred(device); + mCheckedItems[1] = mProfiles[1].isPreferred(device); } break; case Intent.EXTRA_DOCK_STATE_DESK: - items[0] = service.getString(R.string.bluetooth_dock_settings_a2dp); - mProfiles[0] = Profile.A2DP; + items[0] = getString(R.string.bluetooth_dock_settings_a2dp); + mProfiles[0] = mProfileManager.getA2dpProfile(); if (firstTime) { // Disable by default for desk dock mCheckedItems[0] = false; } else { - mCheckedItems[0] = LocalBluetoothProfileManager.getProfileManager(mBtManager, - Profile.A2DP).isPreferred(device); + mCheckedItems[0] = mProfiles[0].isPreferred(device); } break; } return items; } + // TODO: move to background thread to fix strict mode warnings private void handleBtStateChange(Intent intent, int startId) { int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); synchronized (this) { if(DEBUG) Log.d(TAG, "BtState = " + btState + " mPendingDevice = " + mPendingDevice); if (btState == BluetoothAdapter.STATE_ON) { - if (mPendingDevice != null) { - if (mPendingDevice.equals(mDevice)) { - if(DEBUG) Log.d(TAG, "applying settings"); - applyBtSettings(mPendingDevice, mPendingStartId); - } else if(DEBUG) { - Log.d(TAG, "mPendingDevice (" + mPendingDevice + ") != mDevice (" - + mDevice + ")"); - } - - mPendingDevice = null; - DockEventReceiver.finishStartingService(mContext, mPendingStartId); - } else { - if (DEBUG) { - Log.d(TAG, "A DISABLE_BT_WHEN_UNDOCKED = " - + getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED)); - } - // Reconnect if docked and bluetooth was enabled by user. - Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - if (i != null) { - int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - BluetoothDevice device = i - .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device != null) { - connectIfEnabled(device); - } - } else if (getSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT) - && mBtManager.getBluetoothAdapter().disable()) { - mPendingTurnOffStartId = startId; - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT); - return; - } - } - } - - if (mPendingTurnOnStartId != INVALID_STARTID) { - DockEventReceiver.finishStartingService(this, mPendingTurnOnStartId); - mPendingTurnOnStartId = INVALID_STARTID; - } - - DockEventReceiver.finishStartingService(this, startId); + handleBluetoothStateOn(startId); } else if (btState == BluetoothAdapter.STATE_TURNING_OFF) { // Remove the flag to disable BT if someone is turning off bt. // The rational is that: // a) if BT is off at undock time, no work needs to be done // b) if BT is on at undock time, the user wants it on. - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED); + getPrefs().edit().remove(KEY_DISABLE_BT_WHEN_UNDOCKED).apply(); DockEventReceiver.finishStartingService(this, startId); } else if (btState == BluetoothAdapter.STATE_OFF) { // Bluetooth was turning off as we were trying to turn it on. @@ -605,12 +643,12 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli if (mPendingTurnOffStartId != INVALID_STARTID) { DockEventReceiver.finishStartingService(this, mPendingTurnOffStartId); - removeSetting(SHARED_PREFERENCES_KEY_DISABLE_BT); + getPrefs().edit().remove(KEY_DISABLE_BT).apply(); mPendingTurnOffStartId = INVALID_STARTID; } if (mPendingDevice != null) { - mBtManager.getBluetoothAdapter().enable(); + mLocalAdapter.enable(); mPendingTurnOnStartId = startId; } else { DockEventReceiver.finishStartingService(this, startId); @@ -619,86 +657,124 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } } - private void handleUnexpectedDisconnect(BluetoothDevice disconnectedDevice, Profile profile, - int startId) { - synchronized (this) { - if (DEBUG) Log.d(TAG, "handling failed connect for " + disconnectedDevice); + private void handleBluetoothStateOn(int startId) { + if (mPendingDevice != null) { + if (mPendingDevice.equals(mDevice)) { + if(DEBUG) { + Log.d(TAG, "applying settings"); + } + applyBtSettings(mPendingDevice, mPendingStartId); + } else if(DEBUG) { + Log.d(TAG, "mPendingDevice (" + mPendingDevice + ") != mDevice (" + + mDevice + ')'); + } + + mPendingDevice = null; + DockEventReceiver.finishStartingService(this, mPendingStartId); + } else { + final SharedPreferences prefs = getPrefs(); + if (DEBUG) { + Log.d(TAG, "A DISABLE_BT_WHEN_UNDOCKED = " + + prefs.getBoolean(KEY_DISABLE_BT_WHEN_UNDOCKED, false)); + } + // Reconnect if docked and bluetooth was enabled by user. + Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (i != null) { + int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + BluetoothDevice device = i + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device != null) { + connectIfEnabled(device); + } + } else if (prefs.getBoolean(KEY_DISABLE_BT, false) + && mLocalAdapter.disable()) { + mPendingTurnOffStartId = startId; + prefs.edit().remove(KEY_DISABLE_BT).apply(); + return; + } + } + } + + if (mPendingTurnOnStartId != INVALID_STARTID) { + DockEventReceiver.finishStartingService(this, mPendingTurnOnStartId); + mPendingTurnOnStartId = INVALID_STARTID; + } + + DockEventReceiver.finishStartingService(this, startId); + } + + private synchronized void handleUnexpectedDisconnect(BluetoothDevice disconnectedDevice, + LocalBluetoothProfile profile, int startId) { + if (DEBUG) { + Log.d(TAG, "handling failed connect for " + disconnectedDevice); + } // Reconnect if docked. if (disconnectedDevice != null) { // registerReceiver can't be called from a BroadcastReceiver - Intent i = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); - if (i != null) { - int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent intent = registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (intent != null) { + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - BluetoothDevice dockedDevice = i + BluetoothDevice dockedDevice = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (dockedDevice != null && dockedDevice.equals(disconnectedDevice)) { - CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, - mBtManager, dockedDevice); - cachedDevice.connect(profile); + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( + dockedDevice); + cachedDevice.connectProfile(profile); } } } } DockEventReceiver.finishStartingService(this, startId); - } } private synchronized void connectIfEnabled(BluetoothDevice device) { - CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, mBtManager, device); - List<Profile> profiles = cachedDevice.getConnectableProfiles(); - for (int i = 0; i < profiles.size(); i++) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mBtManager, profiles.get(i)); - int auto; - if (Profile.A2DP == profiles.get(i)) { - auto = BluetoothA2dp.PRIORITY_AUTO_CONNECT; - } else if (Profile.HEADSET == profiles.get(i)) { - auto = BluetoothHeadset.PRIORITY_AUTO_CONNECT; - } else { - continue; - } - - if (profileManager.getPreferred(device) == auto) { + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( + device); + List<LocalBluetoothProfile> profiles = cachedDevice.getConnectableProfiles(); + for (LocalBluetoothProfile profile : profiles) { + if (profile.getPreferred(device) == BluetoothProfile.PRIORITY_AUTO_CONNECT) { cachedDevice.connect(false); - break; + return; } } } - private synchronized void applyBtSettings(final BluetoothDevice device, int startId) { - if (device == null || mProfiles == null || mCheckedItems == null) + private synchronized void applyBtSettings(BluetoothDevice device, int startId) { + if (device == null || mProfiles == null || mCheckedItems == null + || mLocalAdapter == null) { return; + } // Turn on BT if something is enabled - synchronized (this) { - for (boolean enable : mCheckedItems) { - if (enable) { - int btState = mBtManager.getBluetoothState(); - if(DEBUG) Log.d(TAG, "BtState = " + btState); - // May have race condition as the phone comes in and out and in the dock. - // Always turn on BT - mBtManager.getBluetoothAdapter().enable(); - - switch (btState) { - case BluetoothAdapter.STATE_OFF: - case BluetoothAdapter.STATE_TURNING_OFF: - case BluetoothAdapter.STATE_TURNING_ON: - if (mPendingDevice != null && mPendingDevice.equals(mDevice)) { - return; - } - - mPendingDevice = device; - mPendingStartId = startId; - if (btState != BluetoothAdapter.STATE_TURNING_ON) { - setSettingBool(SHARED_PREFERENCES_KEY_DISABLE_BT_WHEN_UNDOCKED, - true); - } - return; + for (boolean enable : mCheckedItems) { + if (enable) { + int btState = mLocalAdapter.getBluetoothState(); + if (DEBUG) { + Log.d(TAG, "BtState = " + btState); + } + // May have race condition as the phone comes in and out and in the dock. + // Always turn on BT + mLocalAdapter.enable(); + + // if adapter was previously OFF, TURNING_OFF, or TURNING_ON + if (btState != BluetoothAdapter.STATE_ON) { + if (mPendingDevice != null && mPendingDevice.equals(mDevice)) { + return; + } + + mPendingDevice = device; + mPendingStartId = startId; + if (btState != BluetoothAdapter.STATE_TURNING_ON) { + getPrefs().edit().putBoolean( + KEY_DISABLE_BT_WHEN_UNDOCKED, true).apply(); } + return; } } } @@ -706,28 +782,26 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli mPendingDevice = null; boolean callConnect = false; - CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(mContext, mBtManager, + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice( device); for (int i = 0; i < mProfiles.length; i++) { - LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager - .getProfileManager(mBtManager, mProfiles[i]); - - if (DEBUG) Log.d(TAG, mProfiles[i].toString() + " = " + mCheckedItems[i]); + LocalBluetoothProfile profile = mProfiles[i]; + if (DEBUG) Log.d(TAG, profile.toString() + " = " + mCheckedItems[i]); if (mCheckedItems[i]) { // Checked but not connected callConnect = true; } else if (!mCheckedItems[i]) { // Unchecked, may or may not be connected. - int status = profileManager.getConnectionStatus(cachedDevice.getDevice()); - if (SettingsBtStatus.isConnectionStatusConnected(status)) { + int status = profile.getConnectionStatus(cachedDevice.getDevice()); + if (status == BluetoothProfile.STATE_CONNECTED) { if (DEBUG) Log.d(TAG, "applyBtSettings - Disconnecting"); cachedDevice.disconnect(mProfiles[i]); } } - profileManager.setPreferred(device, mCheckedItems[i]); + profile.setPreferred(device, mCheckedItems[i]); if (DEBUG) { - if (mCheckedItems[i] != profileManager.isPreferred(device)) { + if (mCheckedItems[i] != profile.isPreferred(device)) { Log.e(TAG, "Can't save preferred value"); } } @@ -739,84 +813,47 @@ public class DockService extends Service implements AlertDialog.OnMultiChoiceCli } } - private synchronized void handleDocked(final BluetoothDevice device, final int state, - final int startId) { - if (mBtManager.getDockAutoConnectSetting(device.getAddress())) { + private synchronized void handleDocked(BluetoothDevice device, int state, + int startId) { + if (LocalBluetoothPreferences.getDockAutoConnectSetting(this, device.getAddress())) { // Setting == auto connect - initBtSettings(mContext, device, state, false); + initBtSettings(device, state, false); applyBtSettings(mDevice, startId); } else { - createDialog(mContext, device, state, startId); + createDialog(device, state, startId); } } - private synchronized void handleUndocked(Context context, LocalBluetoothManager localManager, - BluetoothDevice device) { + private synchronized void handleUndocked(BluetoothDevice device) { mRunnable = null; - LocalBluetoothProfileManager.removeServiceListener(this); + mProfileManager.removeServiceListener(this); if (mDialog != null) { mDialog.dismiss(); mDialog = null; } mDevice = null; mPendingDevice = null; - CachedBluetoothDevice cachedBluetoothDevice = getCachedBluetoothDevice(context, - localManager, device); - cachedBluetoothDevice.disconnect(); + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(device); + cachedDevice.disconnect(); } - private static CachedBluetoothDevice getCachedBluetoothDevice(Context context, - LocalBluetoothManager localManager, BluetoothDevice device) { - CachedBluetoothDeviceManager cachedDeviceManager = localManager.getCachedDeviceManager(); - CachedBluetoothDevice cachedBluetoothDevice = cachedDeviceManager.findDevice(device); - if (cachedBluetoothDevice == null) { - cachedBluetoothDevice = new CachedBluetoothDevice(context, device); + private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); } - return cachedBluetoothDevice; - } - - private boolean getSettingBool(String key) { - SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE); - return sharedPref.getBoolean(key, false); - } - - private int getSettingInt(String key, int defaultValue) { - SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE); - return sharedPref.getInt(key, defaultValue); - } - - private void setSettingBool(String key, boolean bool) { - SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE).edit(); - editor.putBoolean(key, bool); - editor.apply(); - } - - private void setSettingInt(String key, int value) { - SharedPreferences.Editor editor = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE).edit(); - editor.putInt(key, value); - editor.apply(); - } - - private void removeSetting(String key) { - SharedPreferences sharedPref = getSharedPreferences(SHARED_PREFERENCES_NAME, - Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.remove(key); - editor.apply(); + return cachedDevice; } public synchronized void onServiceConnected() { if (mRunnable != null) { mRunnable.run(); mRunnable = null; - LocalBluetoothProfileManager.removeServiceListener(this); + mProfileManager.removeServiceListener(this); } } public void onServiceDisconnected() { + // FIXME: shouldn't I do something on service disconnected too? } } diff --git a/src/com/android/settings/bluetooth/HeadsetProfile.java b/src/com/android/settings/bluetooth/HeadsetProfile.java new file mode 100644 index 000000000..dac47b767 --- /dev/null +++ b/src/com/android/settings/bluetooth/HeadsetProfile.java @@ -0,0 +1,198 @@ +/* + * 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.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.Handler; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settings.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * HeadsetProfile handles Bluetooth HFP and Headset profiles. + */ +final class HeadsetProfile implements LocalBluetoothProfile { + private static final String TAG = "HeadsetProfile"; + + private BluetoothHeadset mService; + private boolean mProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final ParcelUuid[] UUIDS = { + BluetoothUuid.HSP, + BluetoothUuid.Handsfree, + }; + + static final String NAME = "HEADSET"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 0; + + // These callbacks run on the main thread. + private final class HeadsetServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mService = (BluetoothHeadset) proxy; + mProfileReady = true; + // We just bound to the service, so refresh the UI of the + // headset device. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (deviceList.isEmpty()) { + return; + } + BluetoothDevice firstDevice = deviceList.get(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(firstDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "HeadsetProfile found new device: " + firstDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, firstDevice); + } + device.onProfileStateChanged(HeadsetProfile.this, + BluetoothProfile.STATE_CONNECTED); + + mProfileManager.callServiceConnectedListeners(); + } + + public void onServiceDisconnected(int profile) { + mProfileReady = false; + mService = null; + mProfileManager.callServiceDisconnectedListeners(); + } + } + + // TODO(): The calls must get queued if mService becomes null. + // It can happen when the phone app crashes for some reason. + // All callers should have service listeners. Dock Service is the only + // one right now. + HeadsetProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(), + BluetoothProfile.HEADSET); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) { + // Downgrade priority as user is disconnecting the headset. + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } else { + return false; + } + } + + public int getConnectionStatus(BluetoothDevice device) { + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public synchronized boolean isProfileReady() { + return mProfileReady; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_headset; + } + + public int getDisconnectResource() { + return R.string.bluetooth_disconnect_headset_profile; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = mService.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_headset_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_headset_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_headset_hfp; + } +} diff --git a/src/com/android/settings/bluetooth/HidProfile.java b/src/com/android/settings/bluetooth/HidProfile.java new file mode 100644 index 000000000..9185059d6 --- /dev/null +++ b/src/com/android/settings/bluetooth/HidProfile.java @@ -0,0 +1,155 @@ +/* + * 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.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settings.R; + +import java.util.List; + +/** + * HidProfile handles Bluetooth HID profile. + */ +final class HidProfile implements LocalBluetoothProfile { + private BluetoothInputDevice mService; + private boolean mProfileReady; + + static final String NAME = "HID"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 3; + + // These callbacks run on the main thread. + private final class InputDeviceServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mService = (BluetoothInputDevice) proxy; + mProfileReady = true; + } + + public void onServiceDisconnected(int profile) { + mProfileReady = false; + mService = null; + } + } + + HidProfile(Context context, LocalBluetoothAdapter adapter) { + adapter.getProfileProxy(context, new InputDeviceServiceListener(), + BluetoothProfile.INPUT_DEVICE); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public boolean isProfileReady() { + return mProfileReady; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_hid; + } + + public int getDisconnectResource() { + return R.string.bluetooth_disconnect_hid_profile; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = mService.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_hid_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_hid_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + if (btClass == null) { + return R.drawable.ic_bt_keyboard_hid; + } + return getHidClassDrawable(btClass); + } + + static int getHidClassDrawable(BluetoothClass btClass) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.PERIPHERAL_KEYBOARD: + case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING: + return R.drawable.ic_bt_keyboard_hid; + case BluetoothClass.Device.PERIPHERAL_POINTING: + return R.drawable.ic_bt_pointing_hid; + default: + return R.drawable.ic_bt_misc_hid; + } + } +} diff --git a/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java b/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java new file mode 100644 index 000000000..013171c14 --- /dev/null +++ b/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java @@ -0,0 +1,216 @@ +/* + * 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.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import java.util.Set; + +/** + * LocalBluetoothAdapter provides an interface between the Settings app + * and the functionality of the local {@link BluetoothAdapter}, specifically + * those related to state transitions of the adapter itself. + * + * <p>Connection and bonding state changes affecting specific devices + * are handled by {@link CachedBluetoothDeviceManager}, + * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}. + */ +public final class LocalBluetoothAdapter { + private static final String TAG = "LocalBluetoothAdapter"; + + /** This class does not allow direct access to the BluetoothAdapter. */ + private final BluetoothAdapter mAdapter; + + private LocalBluetoothProfileManager mProfileManager; + + private static LocalBluetoothAdapter sInstance; + + private int mState = BluetoothAdapter.ERROR; + + private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins + + private long mLastScan; + + private LocalBluetoothAdapter(BluetoothAdapter adapter) { + mAdapter = adapter; + } + + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + /** + * Get the singleton instance of the LocalBluetoothAdapter. If this device + * doesn't support Bluetooth, then null will be returned. Callers must be + * prepared to handle a null return value. + * @return the LocalBluetoothAdapter object, or null if not supported + */ + static synchronized LocalBluetoothAdapter getInstance() { + if (sInstance == null) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + sInstance = new LocalBluetoothAdapter(adapter); + } + } + + return sInstance; + } + + // Pass-through BluetoothAdapter methods that we can intercept if necessary + + void cancelDiscovery() { + mAdapter.cancelDiscovery(); + } + + boolean enable() { + return mAdapter.enable(); + } + + boolean disable() { + return mAdapter.disable(); + } + + void getProfileProxy(Context context, + BluetoothProfile.ServiceListener listener, int profile) { + mAdapter.getProfileProxy(context, listener, profile); + } + + Set<BluetoothDevice> getBondedDevices() { + return mAdapter.getBondedDevices(); + } + + String getName() { + return mAdapter.getName(); + } + + int getScanMode() { + return mAdapter.getScanMode(); + } + + int getState() { + return mAdapter.getState(); + } + + ParcelUuid[] getUuids() { + return mAdapter.getUuids(); + } + + boolean isDiscovering() { + return mAdapter.isDiscovering(); + } + + boolean isEnabled() { + return mAdapter.isEnabled(); + } + + void setDiscoverableTimeout(int timeout) { + mAdapter.setDiscoverableTimeout(timeout); + } + + void setName(String name) { + mAdapter.setName(name); + } + + void setScanMode(int mode) { + mAdapter.setScanMode(mode); + } + + boolean setScanMode(int mode, int duration) { + return mAdapter.setScanMode(mode, duration); + } + + void startScanning(boolean force) { + // Only start if we're not already scanning + if (!mAdapter.isDiscovering()) { + 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. + A2dpProfile a2dp = mProfileManager.getA2dpProfile(); + if (a2dp != null && a2dp.isA2dpPlaying()) { + return; + } + } + + if (mAdapter.startDiscovery()) { + mLastScan = System.currentTimeMillis(); + } + } + } + + void stopScanning() { + if (mAdapter.isDiscovering()) { + mAdapter.cancelDiscovery(); + } + } + + public synchronized int getBluetoothState() { + // Always sync state, in case it changed while paused + syncBluetoothState(); + return mState; + } + + synchronized void setBluetoothStateInt(int state) { + mState = state; + + if (state == BluetoothAdapter.STATE_ON) { + // if mProfileManager hasn't been constructed yet, it will + // get the adapter UUIDs in its constructor when it is. + if (mProfileManager != null) { + mProfileManager.setBluetoothStateOn(); + } + } + } + + // Returns true if the state changed; false otherwise. + boolean syncBluetoothState() { + int currentState = mAdapter.getState(); + if (currentState != mState) { + setBluetoothStateInt(mAdapter.getState()); + return true; + } + return false; + } + + public void setBluetoothEnabled(boolean enabled) { + boolean success = enabled + ? mAdapter.enable() + : mAdapter.disable(); + + if (success) { + setBluetoothStateInt(enabled + ? BluetoothAdapter.STATE_TURNING_ON + : BluetoothAdapter.STATE_TURNING_OFF); + } else { + if (Utils.V) { + Log.v(TAG, "setBluetoothEnabled call, manager didn't return " + + "success for enabled: " + enabled); + } + + syncBluetoothState(); + } + } +} diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java index f3c5e6f30..0c04e2273 100644 --- a/src/com/android/settings/bluetooth/LocalBluetoothManager.java +++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.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,376 +16,96 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - -import android.app.Activity; -import android.app.AlertDialog; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; import android.content.Context; -import android.content.SharedPreferences; -import android.os.ParcelUuid; -import android.util.Config; import android.util.Log; -import android.widget.Toast; - -import java.util.ArrayList; -import java.util.List; -// 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. + * the Bluetooth API. Note that {@link #getInstance} will return null + * if there is no Bluetooth adapter on this device, and callers must be + * prepared to handle this case. */ -public class LocalBluetoothManager { +public final class LocalBluetoothManager { private static final String TAG = "LocalBluetoothManager"; - static final boolean V = Config.LOGV; - static final boolean D = Config.LOGD; - - private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings"; /** Singleton instance. */ private static LocalBluetoothManager sInstance; - private Context mContext; - /** If a BT-related activity is in the foreground, this will be it. */ - private Activity mForegroundActivity; - private AlertDialog mErrorDialog = null; - - private BluetoothAdapter mAdapter; - - private CachedBluetoothDeviceManager mCachedDeviceManager; - private BluetoothA2dp mBluetoothA2dp; - - private int mState = BluetoothAdapter.ERROR; + private final Context mContext; - private final List<Callback> mCallbacks = new ArrayList<Callback>(); - - private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins - - // If a device was picked from the device picker or was in discoverable mode - // in the last 60 seconds, show the pairing dialogs in foreground instead - // of raising notifications - private static final int GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000; - - public static final String SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP = - "last_discovering_time"; - - private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE = - "last_selected_device"; + /** If a BT-related activity is in the foreground, this will be it. */ + private Context mForegroundActivity; - private static final String SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME = - "last_selected_device_time"; + private final LocalBluetoothAdapter mLocalAdapter; - private static final String SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock"; + private final CachedBluetoothDeviceManager mCachedDeviceManager; - private long mLastScan; + /** The Bluetooth profile manager. */ + private final LocalBluetoothProfileManager mProfileManager; - private LocalBluetoothManager() { } + /** The broadcast receiver event manager. */ + private final BluetoothEventManager mEventManager; - public static LocalBluetoothManager getInstance(Context context) { - synchronized (LocalBluetoothManager.class) { - if (sInstance == null) { - sInstance = new LocalBluetoothManager(); - if (!sInstance.init(context)) { - return null; - } - LocalBluetoothProfileManager.init(sInstance); + public static synchronized LocalBluetoothManager getInstance(Context context) { + if (sInstance == null) { + LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance(); + if (adapter == null) { + return null; } - - return sInstance; - } - } - - private boolean init(Context context) { - // This will be around as long as this process is - mContext = context.getApplicationContext(); - - mAdapter = BluetoothAdapter.getDefaultAdapter(); - if (mAdapter == null) { - return false; + // This will be around as long as this process is + Context appContext = context.getApplicationContext(); + sInstance = new LocalBluetoothManager(adapter, appContext); } - mCachedDeviceManager = new CachedBluetoothDeviceManager(this); - - new BluetoothEventRedirector(this).registerReceiver(); + return sInstance; + } - mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP); + private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) { + mContext = context; + mLocalAdapter = adapter; - return true; + mCachedDeviceManager = new CachedBluetoothDeviceManager(); + mEventManager = new BluetoothEventManager(mLocalAdapter, + mCachedDeviceManager); + mProfileManager = new LocalBluetoothProfileManager(context, + mLocalAdapter, mCachedDeviceManager, mEventManager); } - private final BluetoothProfile.ServiceListener mProfileListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mBluetoothA2dp = (BluetoothA2dp) proxy; - } - public void onServiceDisconnected(int profile) { - mBluetoothA2dp = null; - } - }; - - public BluetoothAdapter getBluetoothAdapter() { - return mAdapter; + public LocalBluetoothAdapter getBluetoothAdapter() { + return mLocalAdapter; } public Context getContext() { return mContext; } - public Activity getForegroundActivity() { - return mForegroundActivity; - } - - public void setForegroundActivity(Activity activity) { - if (mErrorDialog != null) { - mErrorDialog.dismiss(); - mErrorDialog = null; - } - mForegroundActivity = activity; + boolean isForegroundActivity() { + return mForegroundActivity != null; } - public SharedPreferences getSharedPreferences() { - return mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - } - - public CachedBluetoothDeviceManager getCachedDeviceManager() { - return mCachedDeviceManager; - } - - 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 (mAdapter.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); + synchronized void setForegroundActivity(Context context) { + if (context != null) { + Log.d(TAG, "setting foreground activity to non-null context"); + mForegroundActivity = context; + mEventManager.resume(context); } 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. - if (mBluetoothA2dp != null) { - List<BluetoothDevice> sinks = mBluetoothA2dp.getConnectedDevices(); - if (sinks.size() > 0) { - if (mBluetoothA2dp.isA2dpPlaying(sinks.get(0))) return; - } - } + if (mForegroundActivity != null) { + Log.d(TAG, "setting foreground activity to null"); + mEventManager.pause(mForegroundActivity); + mForegroundActivity = null; } - - if (mAdapter.startDiscovery()) { - mLastScan = System.currentTimeMillis(); - } - } - } - - public void stopScanning() { - if (mAdapter.isDiscovering()) { - mAdapter.cancelDiscovery(); - } - } - - public int getBluetoothState() { - - if (mState == BluetoothAdapter.ERROR) { - syncBluetoothState(); } - - return mState; } - void setBluetoothStateInt(int state) { - mState = state; - - if (state == BluetoothAdapter.STATE_ON) { - ParcelUuid[] uuids = mAdapter.getUuids(); - LocalBluetoothProfileManager.updateLocalProfiles(getInstance(mContext), uuids); - } - - if (state == BluetoothAdapter.STATE_ON || - state == BluetoothAdapter.STATE_OFF) { - mCachedDeviceManager.onBluetoothStateChanged(state == - BluetoothAdapter.STATE_ON); - } - } - - private void syncBluetoothState() { - int bluetoothState; - - if (mAdapter != null) { - bluetoothState = mAdapter.isEnabled() - ? BluetoothAdapter.STATE_ON - : BluetoothAdapter.STATE_OFF; - } else { - bluetoothState = BluetoothAdapter.ERROR; - } - - setBluetoothStateInt(bluetoothState); - } - - public void setBluetoothEnabled(boolean enabled) { - boolean wasSetStateSuccessful = enabled - ? mAdapter.enable() - : mAdapter.disable(); - - if (wasSetStateSuccessful) { - setBluetoothStateInt(enabled - ? BluetoothAdapter.STATE_TURNING_ON - : BluetoothAdapter.STATE_TURNING_OFF); - } 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) - mCachedDeviceManager.onScanningStateChanged(started); - dispatchScanningStateChanged(started); - } - - private void dispatchScanningStateChanged(boolean started) { - synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onScanningStateChanged(started); - } - } - } - - public void showError(BluetoothDevice device, int messageResId) { - CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device); - String name = null; - if (cachedDevice == null) { - if (device != null) name = device.getName(); - - if (name == null) { - name = mContext.getString(R.string.bluetooth_remote_device); - } - } else { - name = cachedDevice.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(R.string.bluetooth_error_title) - .setMessage(message) - .setPositiveButton(android.R.string.ok, null) - .show(); - } else { - // Fallback on a toast - Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); - } - } - - public interface Callback { - void onScanningStateChanged(boolean started); - void onDeviceAdded(CachedBluetoothDevice cachedDevice); - void onDeviceDeleted(CachedBluetoothDevice cachedDevice); - void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState); - } - - public boolean shouldShowDialogInForeground(String deviceAddress) { - // If Bluetooth Settings is visible - if (mForegroundActivity != null) return true; - - long currentTimeMillis = System.currentTimeMillis(); - SharedPreferences sharedPreferences = getSharedPreferences(); - - // If the device was in discoverABLE mode recently - long lastDiscoverableEndTime = sharedPreferences.getLong( - BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, 0); - if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) - > currentTimeMillis) { - return true; - } - - // If the device was discoverING recently - if (mAdapter != null && mAdapter.isDiscovering()) { - return true; - } else if ((sharedPreferences.getLong(SHARED_PREFERENCES_KEY_DISCOVERING_TIMESTAMP, 0) + - GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) { - return true; - } - - // If the device was picked in the device picker recently - if (deviceAddress != null) { - String lastSelectedDevice = sharedPreferences.getString( - SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, null); - - if (deviceAddress.equals(lastSelectedDevice)) { - long lastDeviceSelectedTime = sharedPreferences.getLong( - SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, 0); - if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) - > currentTimeMillis) { - return true; - } - } - } - return false; - } - - void persistSelectedDeviceInPicker(String deviceAddress) { - SharedPreferences.Editor editor = getSharedPreferences().edit(); - editor.putString(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE, - deviceAddress); - editor.putLong(LocalBluetoothManager.SHARED_PREFERENCES_KEY_LAST_SELECTED_DEVICE_TIME, - System.currentTimeMillis()); - editor.apply(); - } - - public boolean hasDockAutoConnectSetting(String addr) { - return getSharedPreferences().contains(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr); - } - - public boolean getDockAutoConnectSetting(String addr) { - return getSharedPreferences().getBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr, - false); + CachedBluetoothDeviceManager getCachedDeviceManager() { + return mCachedDeviceManager; } - public void saveDockAutoConnectSetting(String addr, boolean autoConnect) { - SharedPreferences.Editor editor = getSharedPreferences().edit(); - editor.putBoolean(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr, autoConnect); - editor.apply(); + BluetoothEventManager getEventManager() { + return mEventManager; } - public void removeDockAutoConnectSetting(String addr) { - SharedPreferences.Editor editor = getSharedPreferences().edit(); - editor.remove(SHARED_PREFERENCES_KEY_DOCK_AUTO_CONNECT + addr); - editor.apply(); + LocalBluetoothProfileManager getProfileManager() { + return mProfileManager; } } diff --git a/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java b/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java new file mode 100644 index 000000000..7e62b0e1a --- /dev/null +++ b/src/com/android/settings/bluetooth/LocalBluetoothPreferences.java @@ -0,0 +1,157 @@ +/* + * 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.QueuedWork; +import android.content.Context; +import android.content.SharedPreferences; + +/** + * LocalBluetoothPreferences provides an interface to the preferences + * related to Bluetooth. + */ +final class LocalBluetoothPreferences { +// private static final String TAG = "LocalBluetoothPreferences"; + + private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings"; + + // If a device was picked from the device picker or was in discoverable mode + // in the last 60 seconds, show the pairing dialogs in foreground instead + // of raising notifications + private static final int GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000; + + private static final String KEY_DISCOVERING_TIMESTAMP = "last_discovering_time"; + + private static final String KEY_LAST_SELECTED_DEVICE = "last_selected_device"; + + private static final String KEY_LAST_SELECTED_DEVICE_TIME = "last_selected_device_time"; + + private static final String KEY_DOCK_AUTO_CONNECT = "auto_connect_to_dock"; + + private static final String KEY_DISCOVERABLE_END_TIMESTAMP = "discoverable_end_timestamp"; + + private LocalBluetoothPreferences() { + } + + private static SharedPreferences getSharedPreferences(Context context) { + return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + } + + static long getDiscoverableEndTimestamp(Context context) { + return getSharedPreferences(context).getLong( + KEY_DISCOVERABLE_END_TIMESTAMP, 0); + } + + static boolean shouldShowDialogInForeground(Context context, + String deviceAddress) { + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context); + if (manager == null) { + return false; + } + + // If Bluetooth Settings is visible + if (manager.isForegroundActivity()) { + return true; + } + + long currentTimeMillis = System.currentTimeMillis(); + SharedPreferences sharedPreferences = getSharedPreferences(context); + + // If the device was in discoverABLE mode recently + long lastDiscoverableEndTime = sharedPreferences.getLong( + KEY_DISCOVERABLE_END_TIMESTAMP, 0); + if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) + > currentTimeMillis) { + return true; + } + + // If the device was discoverING recently + LocalBluetoothAdapter adapter = manager.getBluetoothAdapter(); + if (adapter != null && adapter.isDiscovering()) { + return true; + } else if ((sharedPreferences.getLong(KEY_DISCOVERING_TIMESTAMP, 0) + + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) { + return true; + } + + // If the device was picked in the device picker recently + if (deviceAddress != null) { + String lastSelectedDevice = sharedPreferences.getString( + KEY_LAST_SELECTED_DEVICE, null); + + if (deviceAddress.equals(lastSelectedDevice)) { + long lastDeviceSelectedTime = sharedPreferences.getLong( + KEY_LAST_SELECTED_DEVICE_TIME, 0); + if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) + > currentTimeMillis) { + return true; + } + } + } + return false; + } + + static void persistSelectedDeviceInPicker(Context context, String deviceAddress) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putString(KEY_LAST_SELECTED_DEVICE, + deviceAddress); + editor.putLong(KEY_LAST_SELECTED_DEVICE_TIME, + System.currentTimeMillis()); + editor.apply(); + } + + static void persistDiscoverableEndTimestamp(Context context, long endTimestamp) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putLong(KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp); + editor.apply(); + } + + static void persistDiscoveringTimestamp(final Context context) { + // Load the shared preferences and edit it on a background + // thread (but serialized!). + QueuedWork.singleThreadExecutor().submit(new Runnable() { + public void run() { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putLong( + KEY_DISCOVERING_TIMESTAMP, + System.currentTimeMillis()); + editor.apply(); + } + }); + } + + static boolean hasDockAutoConnectSetting(Context context, String addr) { + return getSharedPreferences(context).contains(KEY_DOCK_AUTO_CONNECT + addr); + } + + static boolean getDockAutoConnectSetting(Context context, String addr) { + return getSharedPreferences(context).getBoolean(KEY_DOCK_AUTO_CONNECT + addr, + false); + } + + static void saveDockAutoConnectSetting(Context context, String addr, boolean autoConnect) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.putBoolean(KEY_DOCK_AUTO_CONNECT + addr, autoConnect); + editor.apply(); + } + + static void removeDockAutoConnectSetting(Context context, String addr) { + SharedPreferences.Editor editor = getSharedPreferences(context).edit(); + editor.remove(KEY_DOCK_AUTO_CONNECT + addr); + editor.apply(); + } +} diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfile.java b/src/com/android/settings/bluetooth/LocalBluetoothProfile.java new file mode 100644 index 000000000..936231a1c --- /dev/null +++ b/src/com/android/settings/bluetooth/LocalBluetoothProfile.java @@ -0,0 +1,76 @@ +/* + * 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.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; + +/** + * LocalBluetoothProfile is an interface defining the basic + * functionality related to a Bluetooth profile. + */ +interface LocalBluetoothProfile { + + /** + * Returns true if the user can initiate a connection, false otherwise. + */ + boolean isConnectable(); + + /** + * Returns true if the user can enable auto connection for this profile. + */ + boolean isAutoConnectable(); + + boolean connect(BluetoothDevice device); + + boolean disconnect(BluetoothDevice device); + + int getConnectionStatus(BluetoothDevice device); + + boolean isPreferred(BluetoothDevice device); + + int getPreferred(BluetoothDevice device); + + void setPreferred(BluetoothDevice device, boolean preferred); + + boolean isProfileReady(); + + /** Display order for device profile settings. */ + int getOrdinal(); + + /** + * Returns the string resource ID for the localized name for this profile. + */ + int getNameResource(); + + /** + * Returns the string resource ID for the disconnect confirmation text + * for this profile. + */ + int getDisconnectResource(); + + /** + * Returns the string resource ID for the summary text for this profile + * for the specified device, e.g. "Use for media audio" or + * "Connected to media audio". + * @param device the device to query for profile connection status + * @return a string resource ID for the profile summary text + */ + int getSummaryResourceForDevice(BluetoothDevice device); + + int getDrawableResource(BluetoothClass btClass); +} diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java index 164d2da32..0bb6f115b 100644 --- a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java +++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.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,67 +16,37 @@ package com.android.settings.bluetooth; -import com.android.settings.R; - import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothInputDevice; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; -import android.os.Handler; +import android.content.Context; +import android.content.Intent; import android.os.ParcelUuid; import android.util.Log; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; import java.util.Map; /** - * LocalBluetoothProfileManager is an abstract class defining the basic - * functionality related to a profile. + * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile + * objects for the available Bluetooth profiles. */ -abstract class LocalBluetoothProfileManager { +final class LocalBluetoothProfileManager { private static final String TAG = "LocalBluetoothProfileManager"; - /* package */ static final ParcelUuid[] HEADSET_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.HSP, - BluetoothUuid.Handsfree, - }; - - /* package */ static final ParcelUuid[] A2DP_SINK_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.AudioSink, - BluetoothUuid.AdvAudioDist, - }; - - /* package */ static final ParcelUuid[] A2DP_SRC_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.AudioSource - }; - - /* package */ static final ParcelUuid[] OPP_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.ObexObjectPush - }; - - /* package */ static final ParcelUuid[] HID_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.Hid - }; - - /* package */ static final ParcelUuid[] PANU_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.PANU - }; - - /* package */ static final ParcelUuid[] NAP_PROFILE_UUIDS = new ParcelUuid[] { - BluetoothUuid.NAP - }; + /** Singleton instance. */ + private static LocalBluetoothProfileManager sInstance; /** * An interface for notifying BluetoothHeadset IPC clients when they have * been connected to the BluetoothHeadset service. + * Only used by {@link DockService}. */ public interface ServiceListener { /** @@ -85,7 +55,7 @@ abstract class LocalBluetoothProfileManager { * this callback before making IPC calls on the BluetoothHeadset * service. */ - public void onServiceConnected(); + void onServiceConnected(); /** * Called to notify the client that this proxy object has been @@ -94,743 +64,246 @@ abstract class LocalBluetoothProfileManager { * This callback will currently only occur if the application hosting * the BluetoothHeadset service, but may be called more often in future. */ - public void onServiceDisconnected(); - } - - // TODO: close profiles when we're shutting down - private static final Map<Profile, LocalBluetoothProfileManager> sProfileMap = - new HashMap<Profile, LocalBluetoothProfileManager>(); - - protected final LocalBluetoothManager mLocalManager; - - public static void init(LocalBluetoothManager localManager) { - synchronized (sProfileMap) { - if (sProfileMap.size() == 0) { - LocalBluetoothProfileManager profileManager; - - profileManager = new A2dpProfileManager(localManager); - sProfileMap.put(Profile.A2DP, profileManager); - - profileManager = new HeadsetProfileManager(localManager); - sProfileMap.put(Profile.HEADSET, profileManager); - - profileManager = new OppProfileManager(localManager); - sProfileMap.put(Profile.OPP, profileManager); - - profileManager = new HidProfileManager(localManager); - sProfileMap.put(Profile.HID, profileManager); - - profileManager = new PanProfileManager(localManager); - sProfileMap.put(Profile.PAN, profileManager); - } - } + void onServiceDisconnected(); } - // TODO(): Combine the init and updateLocalProfiles codes. - // init can get called from various paths, it makes no sense to add and then delete. - public static void updateLocalProfiles(LocalBluetoothManager localManager, ParcelUuid[] uuids) { - if (!BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) && - !BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { - sProfileMap.remove(Profile.HEADSET); - } - - if (!BluetoothUuid.containsAnyUuid(uuids, A2DP_SRC_PROFILE_UUIDS)) { - sProfileMap.remove(Profile.A2DP); - } + private final Context mContext; + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final BluetoothEventManager mEventManager; - if (!BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS)) { - sProfileMap.remove(Profile.OPP); - } - - // There is no local SDP record for HID and Settings app doesn't control PBAP - } - - private static final LinkedList<ServiceListener> mServiceListeners = - new LinkedList<ServiceListener>(); - - public static void addServiceListener(ServiceListener l) { - mServiceListeners.add(l); - } - - public static void removeServiceListener(ServiceListener l) { - mServiceListeners.remove(l); - } - - public static boolean isManagerReady() { - // Getting just the headset profile is fine for now. Will need to deal with A2DP - // and others if they aren't always in a ready state. - LocalBluetoothProfileManager profileManager = sProfileMap.get(Profile.HEADSET); - if (profileManager == null) { - return sProfileMap.size() > 0; - } - return profileManager.isProfileReady(); - } - - public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager, - Profile profile) { - // Note: This code assumes that "localManager" is same as the - // LocalBluetoothManager that was used to initialize the sProfileMap. - // If that every changes, we can't just keep one copy of sProfileMap. - synchronized (sProfileMap) { - LocalBluetoothProfileManager profileManager = sProfileMap.get(profile); - if (profileManager == null) { - Log.e(TAG, "profileManager can't be found for " + profile.toString()); - } - return profileManager; - } - } + private A2dpProfile mA2dpProfile; + private HeadsetProfile mHeadsetProfile; + private final HidProfile mHidProfile; + private OppProfile mOppProfile; + private final PanProfile mPanProfile; /** - * Temporary method to fill profiles based on a device's class. - * - * NOTE: This list happens to define the connection order. We should put this logic in a more - * well known place when this method is no longer temporary. - * @param uuids of the remote device - * @param localUuids UUIDs of the local device - * @param profiles The list of profiles to fill + * Mapping from profile name, e.g. "HEADSET" to profile object. */ - public static void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, - List<Profile> profiles) { - profiles.clear(); + private final Map<String, LocalBluetoothProfile> + mProfileNameMap = new HashMap<String, LocalBluetoothProfile>(); - if (uuids == null) { - return; - } - - if (sProfileMap.containsKey(Profile.HEADSET)) { - if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) && - BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) || - (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) && - BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) { - profiles.add(Profile.HEADSET); - } - } + LocalBluetoothProfileManager(Context context, + LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + BluetoothEventManager eventManager) { + mContext = context; + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mEventManager = eventManager; + // pass this reference to adapter and event manager (circular dependency) + mLocalAdapter.setProfileManager(this); + mEventManager.setProfileManager(this); - if (BluetoothUuid.containsAnyUuid(uuids, A2DP_SINK_PROFILE_UUIDS) && - sProfileMap.containsKey(Profile.A2DP)) { - profiles.add(Profile.A2DP); - } + ParcelUuid[] uuids = adapter.getUuids(); - if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS) && - sProfileMap.containsKey(Profile.OPP)) { - profiles.add(Profile.OPP); + // uuids may be null if Bluetooth is turned off + if (uuids != null) { + updateLocalProfiles(uuids); } - if (BluetoothUuid.containsAnyUuid(uuids, HID_PROFILE_UUIDS) && - sProfileMap.containsKey(Profile.HID)) { - profiles.add(Profile.HID); - } + // Always add HID and PAN profiles + mHidProfile = new HidProfile(context, mLocalAdapter); + addProfile(mHidProfile, HidProfile.NAME, + BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); - if (BluetoothUuid.containsAnyUuid(uuids, NAP_PROFILE_UUIDS) && - sProfileMap.containsKey(Profile.PAN)) { - profiles.add(Profile.PAN); - } - } - - protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) { - mLocalManager = localManager; - } - - public abstract List<BluetoothDevice> getConnectedDevices(); - - public abstract boolean connect(BluetoothDevice device); - - public abstract boolean disconnect(BluetoothDevice device); - - public abstract int getConnectionStatus(BluetoothDevice device); - - public abstract int getSummary(BluetoothDevice device); - - public abstract int convertState(int a2dpState); - - public abstract boolean isPreferred(BluetoothDevice device); - - public abstract int getPreferred(BluetoothDevice device); - - public abstract void setPreferred(BluetoothDevice device, boolean preferred); - - public boolean isConnected(BluetoothDevice device) { - return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device)); - } - - public abstract boolean isProfileReady(); - - public abstract int getDrawableResource(); - - public static enum Profile { - HEADSET(R.string.bluetooth_profile_headset, true, true), - A2DP(R.string.bluetooth_profile_a2dp, true, true), - OPP(R.string.bluetooth_profile_opp, false, false), - HID(R.string.bluetooth_profile_hid, true, true), - PAN(R.string.bluetooth_profile_pan, true, false); - - public final int localizedString; - private final boolean mConnectable; - private final boolean mAutoConnectable; - - private Profile(int localizedString, boolean connectable, - boolean autoConnectable) { - this.localizedString = localizedString; - this.mConnectable = connectable; - this.mAutoConnectable = autoConnectable; - } - - public boolean isConnectable() { - return mConnectable; - } - - public boolean isAutoConnectable() { - return mAutoConnectable; - } + mPanProfile = new PanProfile(context); + addProfile(mPanProfile, PanProfile.NAME, BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); + Log.d(TAG, "LocalBluetoothProfileManager construction complete"); } /** - * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service. + * Initialize or update the local profile objects. If a UUID was previously + * present but has been removed, we print a warning but don't remove the + * profile object as it might be referenced elsewhere, or the UUID might + * come back and we don't want multiple copies of the profile objects. + * @param uuids */ - private static class A2dpProfileManager extends LocalBluetoothProfileManager - implements BluetoothProfile.ServiceListener { - private BluetoothA2dp mService; - - // TODO(): The calls must wait for mService. Its not null just - // because it runs in the system server. - public A2dpProfileManager(LocalBluetoothManager localManager) { - super(localManager); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.A2DP); - - } - - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mService = (BluetoothA2dp) proxy; - } - - public void onServiceDisconnected(int profile) { - mService = null; - } - - @Override - public List<BluetoothDevice> getConnectedDevices() { - return mService.getDevicesMatchingConnectionStates( - new int[] {BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTING}); - } - - @Override - public boolean connect(BluetoothDevice device) { - List<BluetoothDevice> sinks = getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } + void updateLocalProfiles(ParcelUuid[] uuids) { + // A2DP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) { + if (mA2dpProfile == null) { + Log.d(TAG, "Adding local A2DP profile"); + mA2dpProfile = new A2dpProfile(mContext); + addProfile(mA2dpProfile, A2dpProfile.NAME, + BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); } - return mService.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - // Downgrade priority as user is disconnecting the sink. - if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } else if (mA2dpProfile != null) { + Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing."); + } + + // Headset / Handsfree + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) || + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { + if (mHeadsetProfile == null) { + Log.d(TAG, "Adding local HEADSET profile"); + mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter, + mDeviceManager, this); + addProfile(mHeadsetProfile, HeadsetProfile.NAME, + BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); } - return mService.disconnect(device); + } else if (mHeadsetProfile != null) { + Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing."); } - @Override - public int getConnectionStatus(BluetoothDevice device) { - return convertState(mService.getConnectionState(device)); - } - - @Override - public int getSummary(BluetoothDevice device) { - int connectionStatus = getConnectionStatus(device); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_a2dp_profile_summary_connected; - } else { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); + // OPP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + if (mOppProfile == null) { + Log.d(TAG, "Adding local OPP profile"); + mOppProfile = new OppProfile(); + // Note: no event handler for OPP, only name map. + mProfileNameMap.put(OppProfile.NAME, mOppProfile); } + } else if (mOppProfile != null) { + Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing."); } - @Override - public boolean isPreferred(BluetoothDevice device) { - return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; - } - - @Override - public int getPreferred(BluetoothDevice device) { - return mService.getPriority(device); - } - - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (preferred) { - if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - } else { - mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); - } - } - - @Override - public int convertState(int a2dpState) { - switch (a2dpState) { - case BluetoothProfile.STATE_CONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case BluetoothProfile.STATE_CONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case BluetoothProfile.STATE_DISCONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - case BluetoothProfile.STATE_DISCONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; - case BluetoothA2dp.STATE_PLAYING: - return SettingsBtStatus.CONNECTION_STATUS_ACTIVE; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; - } - } - - @Override - public boolean isProfileReady() { - return true; - } - - @Override - public int getDrawableResource() { - return R.drawable.ic_bt_headphones_a2dp; - } + // There is no local SDP record for HID and Settings app doesn't control PBAP } - /** - * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service. - */ - private static class HeadsetProfileManager extends LocalBluetoothProfileManager - implements BluetoothProfile.ServiceListener { - private BluetoothHeadset mService; - private final Handler mUiHandler = new Handler(); - private boolean profileReady = false; - - // TODO(): The calls must get queued if mService becomes null. - // It can happen when phone app crashes for some reason. - // All callers should have service listeners. Dock Service is the only - // one right now. - public HeadsetProfileManager(LocalBluetoothManager localManager) { - super(localManager); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.HEADSET); - } - - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mService = (BluetoothHeadset) proxy; - profileReady = true; - // 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. - */ - List<BluetoothDevice> deviceList = mService.getConnectedDevices(); - if (deviceList.size() == 0) return; - - mLocalManager.getCachedDeviceManager() - .onProfileStateChanged(deviceList.get(0), Profile.HEADSET, - BluetoothProfile.STATE_CONNECTED); - } - }); - - if (mServiceListeners.size() > 0) { - Iterator<ServiceListener> it = mServiceListeners.iterator(); - while(it.hasNext()) { - it.next().onServiceConnected(); - } - } - } - - public void onServiceDisconnected(int profile) { - mService = null; - profileReady = false; - if (mServiceListeners.size() > 0) { - Iterator<ServiceListener> it = mServiceListeners.iterator(); - while(it.hasNext()) { - it.next().onServiceDisconnected(); - } - } - } - - @Override - public boolean isProfileReady() { - return profileReady; - } - - @Override - public List<BluetoothDevice> getConnectedDevices() { - if (mService != null) { - return mService.getConnectedDevices(); - } else { - return new ArrayList<BluetoothDevice>(); - } - } - - @Override - public boolean connect(BluetoothDevice device) { - List<BluetoothDevice> sinks = getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } - } - return mService.connect(device); - } - - @Override - public boolean disconnect(BluetoothDevice device) { - List<BluetoothDevice> deviceList = getConnectedDevices(); - if (deviceList.size() != 0 && deviceList.get(0).equals(device)) { - // Downgrade priority as user is disconnecting the headset. - if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - return mService.disconnect(device); - } else { - return false; - } - } - - @Override - public int getConnectionStatus(BluetoothDevice device) { - List<BluetoothDevice> deviceList = getConnectedDevices(); - - return deviceList.size() > 0 && deviceList.get(0).equals(device) - ? convertState(mService.getConnectionState(device)) - : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - } - - @Override - public int getSummary(BluetoothDevice device) { - int connectionStatus = getConnectionStatus(device); + private final Collection<ServiceListener> mServiceListeners = + new ArrayList<ServiceListener>(); - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_headset_profile_summary_connected; - } else { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); - } - } - - @Override - public boolean isPreferred(BluetoothDevice device) { - return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; - } - - @Override - public int getPreferred(BluetoothDevice device) { - return mService.getPriority(device); - } - - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (preferred) { - if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - } else { - mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); - } - } + private void addProfile(LocalBluetoothProfile profile, + String profileName, String stateChangedAction) { + mEventManager.addHandler(stateChangedAction, new StateChangedHandler(profile)); + mProfileNameMap.put(profileName, profile); + } - @Override - public int convertState(int headsetState) { - switch (headsetState) { - case BluetoothProfile.STATE_CONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case BluetoothProfile.STATE_CONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case BluetoothProfile.STATE_DISCONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; - } - } + LocalBluetoothProfile getProfileByName(String name) { + return mProfileNameMap.get(name); + } - @Override - public int getDrawableResource() { - return R.drawable.ic_bt_headset_hfp; + // Called from LocalBluetoothAdapter when state changes to ON + void setBluetoothStateOn() { + ParcelUuid[] uuids = mLocalAdapter.getUuids(); + if (uuids != null) { + updateLocalProfiles(uuids); } + mEventManager.readPairedDevices(); } /** - * OppProfileManager + * Generic handler for connection state change events for the specified profile. */ - private static class OppProfileManager extends LocalBluetoothProfileManager { - - public OppProfileManager(LocalBluetoothManager localManager) { - super(localManager); - } - - @Override - public List<BluetoothDevice> getConnectedDevices() { - return null; - } - - @Override - public boolean connect(BluetoothDevice device) { - return false; - } + private class StateChangedHandler implements BluetoothEventManager.Handler { + private final LocalBluetoothProfile mProfile; - @Override - public boolean disconnect(BluetoothDevice device) { - return false; + StateChangedHandler(LocalBluetoothProfile profile) { + mProfile = profile; } - @Override - public int getConnectionStatus(BluetoothDevice device) { - return -1; - } - - @Override - public int getSummary(BluetoothDevice device) { - int connectionStatus = getConnectionStatus(device); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_opp_profile_summary_connected; - } else { - return R.string.bluetooth_opp_profile_summary_not_connected; + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "StateChangedHandler found new device: " + device); + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, + LocalBluetoothProfileManager.this, device); } - } - - @Override - public boolean isPreferred(BluetoothDevice device) { - return false; - } - - @Override - public int getPreferred(BluetoothDevice device) { - return -1; - } - - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - } - - @Override - public boolean isProfileReady() { - return true; - } - - @Override - public int convertState(int oppState) { - switch (oppState) { - case 0: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case 1: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case 2: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); + int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); + if (newState == BluetoothProfile.STATE_DISCONNECTED && + oldState == BluetoothProfile.STATE_CONNECTING) { + Log.i(TAG, "Failed to connect " + mProfile + " device"); } - } - @Override - public int getDrawableResource() { - return 0; // no icon for OPP + cachedDevice.onProfileStateChanged(mProfile, newState); + cachedDevice.refresh(); } } - private static class HidProfileManager extends LocalBluetoothProfileManager - implements BluetoothProfile.ServiceListener { - private BluetoothInputDevice mService; - - public HidProfileManager(LocalBluetoothManager localManager) { - super(localManager); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.INPUT_DEVICE); - } - - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mService = (BluetoothInputDevice) proxy; - } - - public void onServiceDisconnected(int profile) { - mService = null; - } - - @Override - public boolean connect(BluetoothDevice device) { - return mService.connect(device); - } - - @Override - public int convertState(int hidState) { - switch (hidState) { - case BluetoothProfile.STATE_CONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case BluetoothProfile.STATE_CONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case BluetoothProfile.STATE_DISCONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - case BluetoothProfile.STATE_DISCONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; - } - } - - @Override - public boolean disconnect(BluetoothDevice device) { - return mService.disconnect(device); - } - - @Override - public List<BluetoothDevice> getConnectedDevices() { - return mService.getConnectedDevices(); - } - - @Override - public int getConnectionStatus(BluetoothDevice device) { - return convertState(mService.getConnectionState(device)); - } - - @Override - public int getPreferred(BluetoothDevice device) { - return mService.getPriority(device); - } - - @Override - public int getSummary(BluetoothDevice device) { - final int connectionStatus = getConnectionStatus(device); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_hid_profile_summary_connected; - } else { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); - } - } - - @Override - public boolean isPreferred(BluetoothDevice device) { - return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; - } + // called from DockService + void addServiceListener(ServiceListener l) { + mServiceListeners.add(l); + } - @Override - public boolean isProfileReady() { - return true; - } + // called from DockService + void removeServiceListener(ServiceListener l) { + mServiceListeners.remove(l); + } - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - if (preferred) { - if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { - mService.setPriority(device, BluetoothProfile.PRIORITY_ON); - } - } else { - mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); - } + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceConnectedListeners() { + for (ServiceListener l : mServiceListeners) { + l.onServiceConnected(); } + } - @Override - public int getDrawableResource() { - return R.drawable.ic_bt_keyboard_hid; + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceDisconnectedListeners() { + for (ServiceListener listener : mServiceListeners) { + listener.onServiceDisconnected(); } } - private static class PanProfileManager extends LocalBluetoothProfileManager - implements BluetoothProfile.ServiceListener { - private BluetoothPan mService; - - public PanProfileManager(LocalBluetoothManager localManager) { - super(localManager); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.PAN); + // This is called by DockService, so check Headset and A2DP. + public synchronized boolean isManagerReady() { + // Getting just the headset profile is fine for now. Will need to deal with A2DP + // and others if they aren't always in a ready state. + LocalBluetoothProfile profile = mHeadsetProfile; + if (profile != null) { + return profile.isProfileReady(); } - - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mService = (BluetoothPan) proxy; + profile = mA2dpProfile; + if (profile != null) { + return profile.isProfileReady(); } + return false; + } - public void onServiceDisconnected(int profile) { - mService = null; - } + A2dpProfile getA2dpProfile() { + return mA2dpProfile; + } - @Override - public boolean connect(BluetoothDevice device) { - List<BluetoothDevice> sinks = getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - mService.disconnect(sink); - } - } - return mService.connect(device); - } + HeadsetProfile getHeadsetProfile() { + return mHeadsetProfile; + } - @Override - public int convertState(int panState) { - switch (panState) { - case BluetoothPan.STATE_CONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTED; - case BluetoothPan.STATE_CONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_CONNECTING; - case BluetoothPan.STATE_DISCONNECTED: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED; - case BluetoothPan.STATE_DISCONNECTING: - return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING; - default: - return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN; - } - } + /** + * Fill in a list of LocalBluetoothProfile objects that are supported by + * the local device and the remote device. + * + * @param uuids of the remote device + * @param localUuids UUIDs of the local device + * @param profiles The list of profiles to fill + */ + synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, + Collection<LocalBluetoothProfile> profiles) { + profiles.clear(); - @Override - public boolean disconnect(BluetoothDevice device) { - return mService.disconnect(device); + if (uuids == null) { + return; } - @Override - public int getSummary(BluetoothDevice device) { - final int connectionStatus = getConnectionStatus(device); - - if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) { - return R.string.bluetooth_pan_profile_summary_connected; - } else { - return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); + if (mHeadsetProfile != null) { + if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) && + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) || + (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) && + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) { + profiles.add(mHeadsetProfile); } } - @Override - public boolean isProfileReady() { - return true; + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && + mA2dpProfile != null) { + profiles.add(mA2dpProfile); } - @Override - public List<BluetoothDevice> getConnectedDevices() { - return mService.getConnectedDevices(); + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) && + mOppProfile != null) { + profiles.add(mOppProfile); } - @Override - public int getConnectionStatus(BluetoothDevice device) { - return convertState(mService.getConnectionState(device)); - } - - @Override - public int getPreferred(BluetoothDevice device) { - return -1; - } - - @Override - public boolean isPreferred(BluetoothDevice device) { - return true; - } - - @Override - public void setPreferred(BluetoothDevice device, boolean preferred) { - // ignore: isPreferred is always true for PAN - return; + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) && + mHidProfile != null) { + profiles.add(mHidProfile); } - @Override - public int getDrawableResource() { - return R.drawable.ic_bt_network_pan; + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) && + mPanProfile != null) { + profiles.add(mPanProfile); } } } diff --git a/src/com/android/settings/bluetooth/OppProfile.java b/src/com/android/settings/bluetooth/OppProfile.java new file mode 100644 index 000000000..7cbae37e8 --- /dev/null +++ b/src/com/android/settings/bluetooth/OppProfile.java @@ -0,0 +1,92 @@ +/* + * 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.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; + +import com.android.settings.R; + +/** + * OppProfile handles Bluetooth OPP. + */ +final class OppProfile implements LocalBluetoothProfile { + + static final String NAME = "OPP"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 2; + + public boolean isConnectable() { + return false; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + return false; + } + + public boolean disconnect(BluetoothDevice device) { + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + return -1; // FIXME: change to DISCONNECTED? + } + + public boolean isPreferred(BluetoothDevice device) { + return false; + } + + public int getPreferred(BluetoothDevice device) { + return -1; // FIXME: is this correct? + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + } + + public boolean isProfileReady() { + return true; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_opp; + } + + public int getDisconnectResource() { + return 0; // user must use notification to disconnect OPP transfer. + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return 0; // OPP profile not displayed in UI + } + + public int getDrawableResource(BluetoothClass btClass) { + return 0; // no icon for OPP + } +} diff --git a/src/com/android/settings/bluetooth/PanProfile.java b/src/com/android/settings/bluetooth/PanProfile.java new file mode 100644 index 000000000..3f456e45b --- /dev/null +++ b/src/com/android/settings/bluetooth/PanProfile.java @@ -0,0 +1,135 @@ +/* + * 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.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settings.R; + +import java.util.List; + +/** + * PanProfile handles Bluetooth PAN profile. + */ +final class PanProfile implements LocalBluetoothProfile { + private BluetoothPan mService; + + static final String NAME = "PAN"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 4; + + // These callbacks run on the main thread. + private final class PanServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mService = (BluetoothPan) proxy; + } + + public void onServiceDisconnected(int profile) { + mService = null; + } + } + + PanProfile(Context context) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + adapter.getProfileProxy(context, new PanServiceListener(), + BluetoothProfile.PAN); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + return true; + } + + public int getPreferred(BluetoothDevice device) { + return -1; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + // ignore: isPreferred is always true for PAN + } + + public boolean isProfileReady() { + return true; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource() { + return R.string.bluetooth_profile_pan; + } + + public int getDisconnectResource() { + return R.string.bluetooth_disconnect_pan_profile; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = mService.getConnectionState(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_pan_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_pan_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_network_pan; + } +} diff --git a/src/com/android/settings/bluetooth/RequestPermissionActivity.java b/src/com/android/settings/bluetooth/RequestPermissionActivity.java index 93d05bc52..07a7316cb 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionActivity.java @@ -27,7 +27,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; @@ -51,7 +50,7 @@ public class RequestPermissionActivity extends Activity implements private static final int REQUEST_CODE_START_BT = 1; - private LocalBluetoothManager mLocalManager; + private LocalBluetoothAdapter mLocalAdapter; private int mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT; @@ -66,18 +65,19 @@ public class RequestPermissionActivity extends Activity implements // True if requesting BT to be turned on // False if requesting BT to be turned on + discoverable mode - private boolean mEnableOnly = false; + private boolean mEnableOnly; - private boolean mUserConfirmed = false; + private boolean mUserConfirmed; - private AlertDialog mDialog = null; + private AlertDialog mDialog; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent == null) + if (intent == null) { return; + } if (mNeededToEnableBluetooth && BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR); @@ -94,12 +94,13 @@ public class RequestPermissionActivity extends Activity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Note: initializes mLocalAdapter and returns true on error if (parseIntent()) { finish(); return; } - int btState = mLocalManager.getBluetoothState(); + int btState = mLocalAdapter.getState(); switch (btState) { case BluetoothAdapter.STATE_OFF: @@ -120,28 +121,29 @@ public class RequestPermissionActivity extends Activity implements */ registerReceiver(mReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); - Intent i = new Intent(); - i.setClass(this, RequestPermissionHelperActivity.class); + Intent intent = new Intent(); + intent.setClass(this, RequestPermissionHelperActivity.class); if (mEnableOnly) { - i.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON); + intent.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON); } else { - i.setAction(RequestPermissionHelperActivity. + intent.setAction(RequestPermissionHelperActivity. ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE); - i.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout); + intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout); } - startActivityForResult(i, REQUEST_CODE_START_BT); + startActivityForResult(intent, REQUEST_CODE_START_BT); mNeededToEnableBluetooth = true; break; case BluetoothAdapter.STATE_ON: if (mEnableOnly) { // Nothing to do. Already enabled. proceedAndFinish(); - return; } else { // Ask the user about enabling discovery mode createDialog(); - break; } + break; + default: + Log.e(TAG, "Unknown adapter state: " + btState); } } @@ -176,8 +178,8 @@ public class RequestPermissionActivity extends Activity implements @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != REQUEST_CODE_START_BT) { - Log.e(TAG, "Unexpected onActivityResult " + requestCode + " " + resultCode); - setResult(Activity.RESULT_CANCELED); + Log.e(TAG, "Unexpected onActivityResult " + requestCode + ' ' + resultCode); + setResult(RESULT_CANCELED); finish(); return; } @@ -191,7 +193,7 @@ public class RequestPermissionActivity extends Activity implements // BT and discoverable mode. mUserConfirmed = true; - if (mLocalManager.getBluetoothState() == BluetoothAdapter.STATE_ON) { + if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) { proceedAndFinish(); } else { // If BT is not up yet, show "Turning on Bluetooth..." @@ -206,7 +208,7 @@ public class RequestPermissionActivity extends Activity implements break; case DialogInterface.BUTTON_NEGATIVE: - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); finish(); break; } @@ -217,18 +219,19 @@ public class RequestPermissionActivity extends Activity implements if (mEnableOnly) { // BT enabled. Done - returnCode = Activity.RESULT_OK; - } else if (mLocalManager.getBluetoothAdapter().setScanMode( + returnCode = RESULT_OK; + } else if (mLocalAdapter.setScanMode( BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mTimeout)) { // If already in discoverable mode, this will extend the timeout. - persistDiscoverableEndTimestamp(System.currentTimeMillis() + mTimeout * 1000); + LocalBluetoothPreferences.persistDiscoverableEndTimestamp( + this, System.currentTimeMillis() + (long) mTimeout * 1000); returnCode = mTimeout; // Activity.RESULT_FIRST_USER should be 1 - if (returnCode < Activity.RESULT_FIRST_USER) { - returnCode = Activity.RESULT_FIRST_USER; + if (returnCode < RESULT_FIRST_USER) { + returnCode = RESULT_FIRST_USER; } } else { - returnCode = Activity.RESULT_CANCELED; + returnCode = RESULT_CANCELED; } if (mDialog != null) { @@ -239,6 +242,10 @@ public class RequestPermissionActivity extends Activity implements finish(); } + /** + * Parse the received Intent and initialize mLocalBluetoothAdapter. + * @return true if an error occurred; false otherwise + */ private boolean parseIntent() { Intent intent = getIntent(); if (intent != null && intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_ENABLE)) { @@ -257,16 +264,17 @@ public class RequestPermissionActivity extends Activity implements Log.e(TAG, "Error: this activity may be started only with intent " + BluetoothAdapter.ACTION_REQUEST_ENABLE + " or " + BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); return true; } - mLocalManager = LocalBluetoothManager.getInstance(this); - if (mLocalManager == null) { - Log.e(TAG, "Error: there's a problem starting bluetooth"); - setResult(Activity.RESULT_CANCELED); + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); + if (manager == null) { + Log.e(TAG, "Error: there's a problem starting Bluetooth"); + setResult(RESULT_CANCELED); return true; } + mLocalAdapter = manager.getBluetoothAdapter(); return false; } @@ -274,20 +282,14 @@ public class RequestPermissionActivity extends Activity implements @Override protected void onDestroy() { super.onDestroy(); - if (mNeededToEnableBluetooth) unregisterReceiver(mReceiver); - } - - private void persistDiscoverableEndTimestamp(long endTimestamp) { - SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit(); - editor.putLong( - BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, - endTimestamp); - editor.apply(); + if (mNeededToEnableBluetooth) { + unregisterReceiver(mReceiver); + } } @Override public void onBackPressed() { - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); super.onBackPressed(); } } diff --git a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java index 2657d91b8..9b5946b59 100644 --- a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java +++ b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java @@ -43,7 +43,7 @@ public class RequestPermissionHelperActivity extends AlertActivity implements public static final String ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE = "com.android.settings.bluetooth.ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE"; - private LocalBluetoothManager mLocalManager; + private LocalBluetoothAdapter mLocalAdapter; private int mTimeout; @@ -55,6 +55,7 @@ public class RequestPermissionHelperActivity extends AlertActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Note: initializes mLocalAdapter and returns true on error if (parseIntent()) { finish(); return; @@ -92,32 +93,33 @@ public class RequestPermissionHelperActivity extends AlertActivity implements public void onClick(DialogInterface dialog, int which) { int returnCode; + // FIXME: fix this ugly switch logic! switch (which) { - case DialogInterface.BUTTON_POSITIVE: + case BUTTON_POSITIVE: int btState = 0; try { // TODO There's a better way. int retryCount = 30; do { - btState = mLocalManager.getBluetoothState(); + btState = mLocalAdapter.getBluetoothState(); Thread.sleep(100); } while (btState == BluetoothAdapter.STATE_TURNING_OFF && --retryCount > 0); - } catch (InterruptedException e) { + } catch (InterruptedException ignored) { // don't care } if (btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON - || mLocalManager.getBluetoothAdapter().enable()) { + || mLocalAdapter.enable()) { returnCode = RequestPermissionActivity.RESULT_BT_STARTING_OR_STARTED; } else { - returnCode = Activity.RESULT_CANCELED; + returnCode = RESULT_CANCELED; } break; - case DialogInterface.BUTTON_NEGATIVE: - returnCode = Activity.RESULT_CANCELED; + case BUTTON_NEGATIVE: + returnCode = RESULT_CANCELED; break; default: return; @@ -125,6 +127,10 @@ public class RequestPermissionHelperActivity extends AlertActivity implements setResult(returnCode); } + /** + * Parse the received Intent and initialize mLocalBluetoothAdapter. + * @return true if an error occurred; false otherwise + */ private boolean parseIntent() { Intent intent = getIntent(); if (intent != null && intent.getAction().equals(ACTION_INTERNAL_REQUEST_BT_ON)) { @@ -136,23 +142,24 @@ public class RequestPermissionHelperActivity extends AlertActivity implements mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT); } else { - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); return true; } - mLocalManager = LocalBluetoothManager.getInstance(this); - if (mLocalManager == null) { - Log.e(TAG, "Error: there's a problem starting bluetooth"); - setResult(Activity.RESULT_CANCELED); + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(this); + if (manager == null) { + Log.e(TAG, "Error: there's a problem starting Bluetooth"); + setResult(RESULT_CANCELED); return true; } + mLocalAdapter = manager.getBluetoothAdapter(); return false; } @Override public void onBackPressed() { - setResult(Activity.RESULT_CANCELED); + setResult(RESULT_CANCELED); super.onBackPressed(); } } diff --git a/src/com/android/settings/bluetooth/SettingsBtStatus.java b/src/com/android/settings/bluetooth/SettingsBtStatus.java deleted file mode 100644 index 2407b533d..000000000 --- a/src/com/android/settings/bluetooth/SettingsBtStatus.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.bluetooth; - -import android.bluetooth.BluetoothDevice; - -import com.android.settings.R; - -/** - * SettingsBtStatus is a helper class that contains constants for various status - * codes. - */ -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 #isConnectionStatusConnected} 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_NONE: - return R.string.bluetooth_not_connected; - default: - return 0; - } - } -} diff --git a/src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java b/src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java new file mode 100644 index 000000000..bae6e5682 --- /dev/null +++ b/src/com/android/settings/bluetooth/Utf8ByteLengthFilter.java @@ -0,0 +1,84 @@ +/* + * 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.text.InputFilter; +import android.text.Spanned; + +/** + * This filter will constrain edits so that the text length is not + * greater than the specified number of bytes using UTF-8 encoding. + * <p>The JNI method used by {@link android.server.BluetoothService} + * to convert UTF-16 to UTF-8 doesn't support surrogate pairs, + * therefore code points outside of the basic multilingual plane + * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters, + * rather than a single 4-byte UTF-8 encoding. Dalvik implements this + * conversion in {@code convertUtf16ToUtf8()} in + * {@code dalvik/vm/UtfString.c}. + * <p>This JNI method is unlikely to change in the future due to + * backwards compatibility requirements. It's also unclear whether + * the installed base of Bluetooth devices would correctly handle the + * encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6. + * However, this filter will still work in scenarios where surrogate + * pairs are encoded as 4 bytes, with the caveat that the maximum + * length will be constrained more conservatively than necessary. + */ +class Utf8ByteLengthFilter implements InputFilter { + private final int mMaxBytes; + + Utf8ByteLengthFilter(int maxBytes) { + mMaxBytes = maxBytes; + } + + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + int srcByteCount = 0; + // count UTF-8 bytes in source substring + for (int i = start; i < end; i++) { + char c = source.charAt(i); + srcByteCount += (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3); + } + int destLen = dest.length(); + int destByteCount = 0; + // count UTF-8 bytes in destination excluding replaced section + for (int i = 0; i < destLen; i++) { + if (i < dstart || i >= dend) { + char c = dest.charAt(i); + destByteCount += (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3); + } + } + int keepBytes = mMaxBytes - destByteCount; + if (keepBytes <= 0) { + return ""; + } else if (keepBytes >= srcByteCount) { + return null; // use original dest string + } else { + // find end position of largest sequence that fits in keepBytes + for (int i = start; i < end; i++) { + char c = source.charAt(i); + keepBytes -= (c < (char) 0x0080) ? 1 : (c < (char) 0x0800 ? 2 : 3); + if (keepBytes < 0) { + return source.subSequence(start, i); + } + } + // If the entire substring fits, we should have returned null + // above, so this line should not be reached. If for some + // reason it is, return null to use the original dest string. + return null; + } + } +} diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java new file mode 100644 index 000000000..7d38e1700 --- /dev/null +++ b/src/com/android/settings/bluetooth/Utils.java @@ -0,0 +1,99 @@ +/* + * 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.AlertDialog; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.DialogInterface; +import android.widget.Toast; + +import com.android.settings.R; + +/** + * Utils is a helper class that contains constants for various + * Android resource IDs, debug logging flags, and static methods + * for creating dialogs. + */ +final class Utils { + static final boolean V = false; // verbose logging + static final boolean D = true; // regular logging + + private Utils() { + } + + public static int getConnectionStateSummary(int connectionState) { + switch (connectionState) { + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_connected; + case BluetoothProfile.STATE_CONNECTING: + return R.string.bluetooth_connecting; + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_disconnected; + case BluetoothProfile.STATE_DISCONNECTING: + return R.string.bluetooth_disconnecting; + default: + return 0; + } + } + + // Create (or recycle existing) and show disconnect dialog. + static AlertDialog showDisconnectDialog(Context context, + AlertDialog dialog, + DialogInterface.OnClickListener disconnectListener, + CharSequence title, CharSequence message) { + if (dialog == null) { + dialog = new AlertDialog.Builder(context) + .setPositiveButton(android.R.string.ok, disconnectListener) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } else { + if (dialog.isShowing()) { + dialog.dismiss(); + } + // use disconnectListener for the correct profile(s) + CharSequence okText = context.getText(android.R.string.ok); + dialog.setButton(DialogInterface.BUTTON_POSITIVE, + okText, disconnectListener); + } + dialog.setTitle(title); + dialog.setMessage(message); + dialog.show(); + return dialog; + } + + // TODO: wire this up to show connection errors... + static void showConnectingError(Context context, String name) { + // if (!mIsConnectingErrorPossible) { + // return; + // } + // mIsConnectingErrorPossible = false; + + showError(context, name, R.string.bluetooth_connecting_error_message); + } + + static void showError(Context context, String name, int messageResId) { + String message = context.getString(messageResId, name); + new AlertDialog.Builder(context) + .setIcon(android.R.drawable.ic_dialog_alert) + .setTitle(R.string.bluetooth_error_title) + .setMessage(message) + .setPositiveButton(android.R.string.ok, null) + .show(); + } +} diff --git a/src/com/android/settings/widget/SettingsAppWidgetProvider.java b/src/com/android/settings/widget/SettingsAppWidgetProvider.java index 8f17e05e6..ba2b61541 100644 --- a/src/com/android/settings/widget/SettingsAppWidgetProvider.java +++ b/src/com/android/settings/widget/SettingsAppWidgetProvider.java @@ -38,6 +38,7 @@ import android.provider.Settings; import android.util.Log; import android.widget.RemoteViews; import com.android.settings.R; +import com.android.settings.bluetooth.LocalBluetoothAdapter; import com.android.settings.bluetooth.LocalBluetoothManager; /** @@ -50,7 +51,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { new ComponentName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider"); - private static LocalBluetoothManager sLocalBluetoothManager = null; + private static LocalBluetoothAdapter sLocalBluetoothAdapter = null; private static final int BUTTON_WIFI = 0; private static final int BUTTON_BRIGHTNESS = 1; @@ -411,18 +412,19 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { @Override public int getActualState(Context context) { - if (sLocalBluetoothManager == null) { - sLocalBluetoothManager = LocalBluetoothManager.getInstance(context); - if (sLocalBluetoothManager == null) { + if (sLocalBluetoothAdapter == null) { + LocalBluetoothManager manager = LocalBluetoothManager.getInstance(context); + if (manager == null) { return STATE_UNKNOWN; // On emulator? } + sLocalBluetoothAdapter = manager.getBluetoothAdapter(); } - return bluetoothStateToFiveState(sLocalBluetoothManager.getBluetoothState()); + return bluetoothStateToFiveState(sLocalBluetoothAdapter.getBluetoothState()); } @Override protected void requestStateChange(Context context, final boolean desiredState) { - if (sLocalBluetoothManager == null) { + if (sLocalBluetoothAdapter == null) { Log.d(TAG, "No LocalBluetoothManager"); return; } @@ -433,7 +435,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... args) { - sLocalBluetoothManager.setBluetoothEnabled(desiredState); + sLocalBluetoothAdapter.setBluetoothEnabled(desiredState); return null; } }.execute(); @@ -584,7 +586,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // Update each requested appWidgetId - RemoteViews view = buildUpdate(context, -1); + RemoteViews view = buildUpdate(context); for (int i = 0; i < appWidgetIds.length; i++) { appWidgetManager.updateAppWidget(appWidgetIds[i], view); @@ -613,22 +615,22 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { /** * Load image for given widget and build {@link RemoteViews} for it. */ - static RemoteViews buildUpdate(Context context, int appWidgetId) { + static RemoteViews buildUpdate(Context context) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget); - views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, appWidgetId, + views.setOnClickPendingIntent(R.id.btn_wifi, getLaunchPendingIntent(context, BUTTON_WIFI)); views.setOnClickPendingIntent(R.id.btn_brightness, getLaunchPendingIntent(context, - appWidgetId, BUTTON_BRIGHTNESS)); + BUTTON_BRIGHTNESS)); views.setOnClickPendingIntent(R.id.btn_sync, getLaunchPendingIntent(context, - appWidgetId, BUTTON_SYNC)); + BUTTON_SYNC)); views.setOnClickPendingIntent(R.id.btn_gps, - getLaunchPendingIntent(context, appWidgetId, BUTTON_GPS)); + getLaunchPendingIntent(context, BUTTON_GPS)); views.setOnClickPendingIntent(R.id.btn_bluetooth, getLaunchPendingIntent(context, - appWidgetId, BUTTON_BLUETOOTH)); + BUTTON_BLUETOOTH)); updateButtons(views, context); return views; @@ -640,7 +642,7 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { * @param context */ public static void updateWidget(Context context) { - RemoteViews views = buildUpdate(context, -1); + RemoteViews views = buildUpdate(context); // Update specific list of appWidgetIds if given, otherwise default to all final AppWidgetManager gm = AppWidgetManager.getInstance(context); gm.updateAppWidget(THIS_APPWIDGET, views); @@ -680,10 +682,9 @@ public class SettingsAppWidgetProvider extends AppWidgetProvider { * Creates PendingIntent to notify the widget of a button click. * * @param context - * @param appWidgetId * @return */ - private static PendingIntent getLaunchPendingIntent(Context context, int appWidgetId, + private static PendingIntent getLaunchPendingIntent(Context context, int buttonId) { Intent launchIntent = new Intent(); launchIntent.setClass(context, SettingsAppWidgetProvider.class); |