diff options
Diffstat (limited to 'src/com')
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); |