diff options
author | Jason Monk <jmonk@google.com> | 2015-02-24 13:23:29 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-02-24 13:23:31 +0000 |
commit | 08238bb6247a2c1d93aefd52a31f9d1ad5bc7ff0 (patch) | |
tree | 1a78184efe0e4dcb6dd49cdd5e1e04483c89c9bc | |
parent | fb66b8ff48491ce7f537955aedc143e9fb69cca1 (diff) | |
parent | 7ce96b9e610de2782ec5f2af806e7bc0f90c8578 (diff) | |
download | frameworks_base-08238bb6247a2c1d93aefd52a31f9d1ad5bc7ff0.zip frameworks_base-08238bb6247a2c1d93aefd52a31f9d1ad5bc7ff0.tar.gz frameworks_base-08238bb6247a2c1d93aefd52a31f9d1ad5bc7ff0.tar.bz2 |
Merge "Move non-ui bt settings code to SettingsLib"
57 files changed, 3803 insertions, 0 deletions
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..6e29d23 --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..6110e9e --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..6cca225 --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..6445f2a --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..78c0294 --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..2fcc3b0 --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..70d35bf --- /dev/null +++ b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..2d9b75e --- /dev/null +++ b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..b6ebe34 --- /dev/null +++ b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..8b67b91 --- /dev/null +++ b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..1fa0a3d --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..175bd78 --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..05b27e8 --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..6e9f8ae --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..5f3371f --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..539d77f --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..3216776 --- /dev/null +++ b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..4f381ba --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..c67127d --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..d3b356b --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..2d38129 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..fb76575 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..d8b68eb --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..02cc3af --- /dev/null +++ b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..7805b7a --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..8127774 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..84b8085 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..289d6ac --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..72bc804 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..e31ce2b --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..f23b0e7 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png Binary files differnew file mode 100644 index 0000000..1e12f96 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png Binary files differnew file mode 100644 index 0000000..8b547d9 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png Binary files differnew file mode 100644 index 0000000..03c5033 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png Binary files differnew file mode 100644 index 0000000..b9a9923 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png Binary files differnew file mode 100644 index 0000000..989e1ab --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png Binary files differnew file mode 100644 index 0000000..de8c389 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png Binary files differnew file mode 100644 index 0000000..2eb8a92 --- /dev/null +++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f055a2c..870afeb 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -67,4 +67,87 @@ <!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] --> <string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string> + + <!-- Bluetooth settings. Message when a device is disconnected --> + <string name="bluetooth_disconnected">Disconnected</string> + <!-- Bluetooth settings. Message when disconnecting from a device --> + <string name="bluetooth_disconnecting">Disconnecting\u2026</string> + <!-- Bluetooth settings. Message when connecting to a device --> + <string name="bluetooth_connecting">Connecting\u2026</string> + <!-- Bluetooth settings. Message when connected to a device. [CHAR LIMIT=40] --> + <string name="bluetooth_connected">Connected</string> + + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. --> + <string name="bluetooth_profile_a2dp">Media audio</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the headset or handsfree profile. --> + <string name="bluetooth_profile_headset">Phone audio</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the OPP profile. --> + <string name="bluetooth_profile_opp">File transfer</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the HID profile. --> + <string name="bluetooth_profile_hid">Input device</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] --> + <string name="bluetooth_profile_pan">Internet access</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] --> + <string name="bluetooth_profile_pbap">Contact sharing</string> + <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] --> + <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] --> + <string name="bluetooth_profile_pan_nap">Internet connection sharing</string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. --> + <string name="bluetooth_profile_map">Message Access</string> + + <!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference when A2DP is connected. --> + <string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string> + <!-- Bluetooth settings. Connection options screen. The summary for the headset checkbox preference when headset is connected. --> + <string name="bluetooth_headset_profile_summary_connected">Connected to phone audio</string> + <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference when OPP is connected. --> + <string name="bluetooth_opp_profile_summary_connected">Connected to file transfer server</string> + <!-- Bluetooth settings. Connection options screen. The summary for the map checkbox preference when map is connected. --> + <string name="bluetooth_map_profile_summary_connected">Connected to map</string> + <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference when OPP is not connected. --> + <string name="bluetooth_opp_profile_summary_not_connected">Not connected to file transfer server</string> + <!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. --> + <string name="bluetooth_hid_profile_summary_connected">Connected to input device</string> + <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]--> + <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for Internet access</string> + <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]--> + <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local Internet connection with device</string> + + <!-- Bluetooth settings. Connection options screen. The summary + for the PAN checkbox preference that describes how checking it + will set the PAN profile as preferred. --> + <string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string> + <!-- Bluetooth settings. Connection options screen. The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. --> + <string name="bluetooth_map_profile_summary_use_for">Use for map</string> + <!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference that describes how checking it will set the A2DP profile as preferred. --> + <string name="bluetooth_a2dp_profile_summary_use_for">Use for media audio</string> + <!-- Bluetooth settings. Connection options screen. The summary for the headset checkbox preference that describes how checking it will set the headset profile as preferred. --> + <string name="bluetooth_headset_profile_summary_use_for">Use for phone audio</string> + <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference that describes how checking it will set the OPP profile as preferred. --> + <string name="bluetooth_opp_profile_summary_use_for">Use for file transfer</string> + <!-- Bluetooth settings. Connection options screen. The summary + for the HID checkbox preference that describes how checking it + will set the HID profile as preferred. --> + <string name="bluetooth_hid_profile_summary_use_for">Use for input</string> + + <!-- Button text for accepting an incoming pairing request. [CHAR LIMIT=20] --> + <string name="bluetooth_pairing_accept">Pair</string> + <!-- Button text for accepting an incoming pairing request in all caps. [CHAR LIMIT=20] --> + <string name="bluetooth_pairing_accept_all_caps">PAIR</string> + <!-- Button text for declining an incoming pairing request. [CHAR LIMIT=20] --> + <string name="bluetooth_pairing_decline">Cancel</string> + + <!-- Message in pairing dialogs. [CHAR LIMIT=NONE] --> + <string name="bluetooth_pairing_will_share_phonebook">Pairing grants access to your contacts and call history when connected.</string> + <!-- Message for the error dialog when BT pairing fails generically. --> + <string name="bluetooth_pairing_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g>.</string> + + <!-- Message for the error dialog when BT pairing fails because the PIN / + Passkey entered is incorrect. --> + <string name="bluetooth_pairing_pin_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g> because of an incorrect PIN or passkey.</string> + <!-- Message for the error dialog when BT pairing fails because the other device is down. --> + <string name="bluetooth_pairing_device_down_error_message">Can\'t communicate with <xliff:g id="device_name">%1$s</xliff:g>.</string> + <!-- Message for the error dialog when BT pairing fails because the other device rejected the pairing. --> + <string name="bluetooth_pairing_rejected_error_message">Pairing rejected by <xliff:g id="device_name">%1$s</xliff:g>.</string> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java new file mode 100755 index 0000000..9608daa --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -0,0 +1,214 @@ +/* + * 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.settingslib.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 android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +public final class A2dpProfile implements LocalBluetoothProfile { + private static final String TAG = "A2dpProfile"; + private static boolean V = false; + + private BluetoothA2dp mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + + static final ParcelUuid[] SINK_UUIDS = { + BluetoothUuid.AudioSink, + BluetoothUuid.AdvAudioDist, + }; + + static final String NAME = "A2DP"; + private final LocalBluetoothProfileManager mProfileManager; + + // 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) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothA2dp) proxy; + // We just bound to the service, so refresh the UI for any connected A2DP devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "A2dpProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + A2dpProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(), + BluetoothProfile.A2DP); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public List<BluetoothDevice> getConnectedDevices() { + if (mService == null) return new ArrayList<BluetoothDevice>(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + List<BluetoothDevice> sinks = getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + // 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); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + boolean isA2dpPlaying() { + if (mService == null) return false; + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (!sinks.isEmpty()) { + if (mService.isA2dpPlaying(sinks.get(0))) { + return true; + } + } + return false; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_a2dp; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(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; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up A2DP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java new file mode 100644 index 0000000..b802f58 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -0,0 +1,30 @@ +/* + * 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.settingslib.bluetooth; + + +/** + * BluetoothCallback provides a callback interface for the settings + * UI to receive events from {@link BluetoothEventManager}. + */ +public 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/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java new file mode 100644 index 0000000..8dec86a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java @@ -0,0 +1,169 @@ +/* + * 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.settingslib.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}. + */ +public final class BluetoothDeviceFilter { + private static final String TAG = "BluetoothDeviceFilter"; + + /** The filter interface to external classes. */ + public interface Filter { + boolean matches(BluetoothDevice device); + } + + /** All filter singleton (referenced directly). */ + public static final Filter ALL_FILTER = new AllFilter(); + + /** Bonded devices only filter (referenced directly). */ + public static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter(); + + /** Unbonded devices only filter (referenced directly). */ + public static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter(); + + /** 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. + */ + public 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; + } + } + + /** Filter that matches only unbonded devices. */ + private static final class UnbondedDeviceFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return device.getBondState() != BluetoothDevice.BOND_BONDED; + } + } + + /** Parent class of filters based on UUID and/or Bluetooth class. */ + private abstract static class ClassUuidFilter implements Filter { + abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass); + + 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/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java new file mode 100644 index 0000000..acb7e7a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012 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.settingslib.bluetooth; + +/* Required to handle timeout notification when phone is suspended */ +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + + +public class BluetoothDiscoverableTimeoutReceiver extends BroadcastReceiver { + private static final String TAG = "BluetoothDiscoverableTimeoutReceiver"; + + private static final String INTENT_DISCOVERABLE_TIMEOUT = + "android.bluetooth.intent.DISCOVERABLE_TIMEOUT"; + + public static void setDiscoverableAlarm(Context context, long alarmTime) { + Log.d(TAG, "setDiscoverableAlarm(): alarmTime = " + alarmTime); + + Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT); + intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class); + PendingIntent pending = PendingIntent.getBroadcast( + context, 0, intent, 0); + AlarmManager alarmManager = + (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); + + if (pending != null) { + // Cancel any previous alarms that do the same thing. + alarmManager.cancel(pending); + Log.d(TAG, "setDiscoverableAlarm(): cancel prev alarm"); + } + pending = PendingIntent.getBroadcast( + context, 0, intent, 0); + + alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pending); + } + + public static void cancelDiscoverableAlarm(Context context) { + Log.d(TAG, "cancelDiscoverableAlarm(): Enter"); + + Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT); + intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class); + PendingIntent pending = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_NO_CREATE); + if (pending != null) { + // Cancel any previous alarms that do the same thing. + AlarmManager alarmManager = + (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); + + alarmManager.cancel(pending); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + LocalBluetoothAdapter localBluetoothAdapter = LocalBluetoothAdapter.getInstance(); + + if(localBluetoothAdapter != null && + localBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { + Log.d(TAG, "Disable discoverable..."); + + localBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + } else { + Log.e(TAG, "localBluetoothAdapter is NULL!!"); + } + } +}; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java new file mode 100755 index 0000000..7c92368 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -0,0 +1,362 @@ +/* + * 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.settingslib.bluetooth; + +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 com.android.settingslib.R; + +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. + */ +public final class BluetoothEventManager { + private static final String TAG = "BluetoothEventManager"; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private LocalBluetoothProfileManager mProfileManager; + private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter; + private final Map<String, Handler> mHandlerMap; + private Context mContext; + + 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); + mAdapterIntentFilter.addAction(action); + } + + void addProfileHandler(String action, Handler handler) { + mHandlerMap.put(action, handler); + mProfileIntentFilter.addAction(action); + } + + // Set profile manager after construction due to circular dependency + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + BluetoothEventManager(LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, Context context) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mAdapterIntentFilter = new IntentFilter(); + mProfileIntentFilter = new IntentFilter(); + mHandlerMap = new HashMap<String, Handler>(); + mContext = context; + + // 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()); + + mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter); + } + + void registerProfileIntentReceiver() { + mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter); + } + + /** Register to start receiving callbacks for Bluetooth events. */ + public void registerCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.add(callback); + } + } + + /** Unregister to stop receiving callbacks for Bluetooth events. */ + public void unregisterCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + 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); + } + } + // Inform CachedDeviceManager that the adapter state has changed + mDeviceManager.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); + } + } + + 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.setNewName(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 (CachedBluetoothDeviceManager.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) { + 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) { + cachedDevice.setVisible(false); + } + } + } + } + } + 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/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java new file mode 100755 index 0000000..ddcc49f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -0,0 +1,787 @@ +/* + * 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.ParcelUuid; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Log; +import android.bluetooth.BluetoothAdapter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +/** + * CachedBluetoothDevice represents a remote Bluetooth device. It contains + * attributes of the device (such as the address, name, RSSI, etc.) and + * functionality that can be performed on the device (connect, pair, disconnect, + * etc.). + */ +public final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { + private static final String TAG = "CachedBluetoothDevice"; + private static final boolean DEBUG = Utils.V; + + private final Context mContext; + private final LocalBluetoothAdapter mLocalAdapter; + private final LocalBluetoothProfileManager mProfileManager; + private final BluetoothDevice mDevice; + private String mName; + private short mRssi; + private BluetoothClass mBtClass; + private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState; + + private final List<LocalBluetoothProfile> mProfiles = + new ArrayList<LocalBluetoothProfile>(); + + // List of profiles that were previously in mProfiles, but have been removed + private final List<LocalBluetoothProfile> mRemovedProfiles = + new ArrayList<LocalBluetoothProfile>(); + + // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP + private boolean mLocalNapRoleConnected; + + private boolean mVisible; + + private int mPhonebookPermissionChoice; + + private int mMessagePermissionChoice; + + private int mMessageRejectionCount; + + private final Collection<Callback> mCallbacks = new ArrayList<Callback>(); + + // Following constants indicate the user's choices of Phone book/message access settings + // User hasn't made any choice or settings app has wiped out the memory + public final static int ACCESS_UNKNOWN = 0; + // User has accepted the connection and let Settings app remember the decision + public final static int ACCESS_ALLOWED = 1; + // User has rejected the connection and let Settings app remember the decision + public final static int ACCESS_REJECTED = 2; + + // How many times user should reject the connection to make the choice persist. + private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2; + + private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject"; + + /** + * When we connect to multiple profiles, we only want to display a single + * error even if they all fail. This tracks that state. + */ + private boolean mIsConnectingErrorPossible; + + /** + * Last time a bt profile auto-connect was attempted. + * If an ACTION_UUID intent comes in within + * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect + * again with the new UUIDs + */ + private long mConnectAttempted; + + // See mConnectAttempted + private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; + + /** Auto-connect after pairing only if locally initiated. */ + private boolean mConnectAfterPairing; + + /** + * Describes the current device and profile for logging. + * + * @param profile Profile to describe + * @return Description of the device and profile + */ + private String describe(LocalBluetoothProfile profile) { + StringBuilder sb = new StringBuilder(); + sb.append("Address:").append(mDevice); + if (profile != null) { + sb.append(" Profile:").append(profile); + } + + return sb.toString(); + } + + void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { + if (Utils.D) { + Log.d(TAG, "onProfileStateChanged: profile " + profile + + " newProfileState " + newProfileState); + } + if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) + { + if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored..."); + return; + } + mProfileConnectionState.put(profile, newProfileState); + if (newProfileState == BluetoothProfile.STATE_CONNECTED) { + if (profile instanceof MapProfile) { + profile.setPreferred(mDevice, true); + } else if (!mProfiles.contains(profile)) { + mRemovedProfiles.remove(profile); + mProfiles.add(profile); + if (profile instanceof PanProfile && + ((PanProfile) profile).isLocalRoleNap(mDevice)) { + // Device doesn't support NAP, so remove PanProfile on disconnect + mLocalNapRoleConnected = true; + } + } + } else if (profile instanceof MapProfile && + newProfileState == BluetoothProfile.STATE_DISCONNECTED) { + profile.setPreferred(mDevice, false); + } else if (mLocalNapRoleConnected && profile instanceof PanProfile && + ((PanProfile) profile).isLocalRoleNap(mDevice) && + newProfileState == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "Removing PanProfile from device after NAP disconnect"); + mProfiles.remove(profile); + mRemovedProfiles.add(profile); + mLocalNapRoleConnected = false; + } + } + + CachedBluetoothDevice(Context context, + LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + mContext = context; + mLocalAdapter = adapter; + mProfileManager = profileManager; + mDevice = device; + mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>(); + fillData(); + } + + public void disconnect() { + for (LocalBluetoothProfile profile : mProfiles) { + disconnect(profile); + } + // Disconnect PBAP server in case its connected + // This is to ensure all the profiles are disconnected as some CK/Hs do not + // disconnect PBAP connection when HF connection is brought down + PbapServerProfile PbapProfile = mProfileManager.getPbapProfile(); + if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED) + { + PbapProfile.disconnect(mDevice); + } + } + + public void disconnect(LocalBluetoothProfile profile) { + if (profile.disconnect(mDevice)) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); + } + } + } + + public void connect(boolean connectAllProfiles) { + if (!ensurePaired()) { + return; + } + + mConnectAttempted = SystemClock.elapsedRealtime(); + connectWithoutResettingTimer(connectAllProfiles); + } + + void onBondingDockConnect() { + // Attempt to connect if UUIDs are available. Otherwise, + // we will connect when the ACTION_UUID intent arrives. + connect(false); + } + + private void connectWithoutResettingTimer(boolean connectAllProfiles) { + // Try to initialize the profiles if they were not. + if (mProfiles.isEmpty()) { + // if mProfiles is empty, then do not invoke updateProfiles. This causes a race + // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated + // from bluetooth stack but ACTION.uuid is not sent yet. + // Eventually ACTION.uuid will be received which shall trigger the connection of the + // various profiles + // If UUIDs are not available yet, connect will be happen + // upon arrival of the ACTION_UUID intent. + Log.d(TAG, "No profiles. Maybe we will connect later"); + return; + } + + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + + int preferredProfiles = 0; + for (LocalBluetoothProfile profile : mProfiles) { + if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) { + if (profile.isPreferred(mDevice)) { + ++preferredProfiles; + connectInt(profile); + } + } + } + if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles); + + if (preferredProfiles == 0) { + connectAutoConnectableProfiles(); + } + } + + private void connectAutoConnectableProfiles() { + if (!ensurePaired()) { + return; + } + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + + for (LocalBluetoothProfile profile : mProfiles) { + if (profile.isAutoConnectable()) { + profile.setPreferred(mDevice, true); + connectInt(profile); + } + } + } + + /** + * Connect this device to the specified profile. + * + * @param profile the profile to use with the remote device + */ + public void connectProfile(LocalBluetoothProfile profile) { + mConnectAttempted = SystemClock.elapsedRealtime(); + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + connectInt(profile); + // Refresh the UI based on profile.connect() call + refresh(); + } + + synchronized void connectInt(LocalBluetoothProfile profile) { + if (!ensurePaired()) { + return; + } + if (profile.connect(mDevice)) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); + } + return; + } + Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName); + } + + private boolean ensurePaired() { + if (getBondState() == BluetoothDevice.BOND_NONE) { + startPairing(); + return false; + } else { + return true; + } + } + + public boolean startPairing() { + // Pairing is unreliable while scanning, so cancel discovery + if (mLocalAdapter.isDiscovering()) { + mLocalAdapter.cancelDiscovery(); + } + + if (!mDevice.createBond()) { + return false; + } + + mConnectAfterPairing = true; // auto-connect after pairing + return true; + } + + /** + * Return true if user initiated pairing on this device. The message text is + * slightly different for local vs. remote initiated pairing dialogs. + */ + boolean isUserInitiatedPairing() { + return mConnectAfterPairing; + } + + public void unpair() { + int state = getBondState(); + + if (state == BluetoothDevice.BOND_BONDING) { + mDevice.cancelBondProcess(); + } + + if (state != BluetoothDevice.BOND_NONE) { + final BluetoothDevice dev = mDevice; + if (dev != null) { + final boolean successful = dev.removeBond(); + if (successful) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null)); + } + } else if (Utils.V) { + Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " + + describe(null)); + } + } + } + } + + public int getProfileConnectionState(LocalBluetoothProfile profile) { + if (mProfileConnectionState == null || + mProfileConnectionState.get(profile) == null) { + // If cache is empty make the binder call to get the state + int state = profile.getConnectionStatus(mDevice); + mProfileConnectionState.put(profile, state); + } + return mProfileConnectionState.get(profile); + } + + public void clearProfileConnectionState () + { + if (Utils.D) { + Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName()); + } + for (LocalBluetoothProfile profile :getProfiles()) { + mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED); + } + } + + // TODO: do any of these need to run async on a background thread? + private void fillData() { + fetchName(); + fetchBtClass(); + updateProfiles(); + migratePhonebookPermissionChoice(); + migrateMessagePermissionChoice(); + fetchMessageRejectionCount(); + + mVisible = false; + dispatchAttributesChanged(); + } + + public BluetoothDevice getDevice() { + return mDevice; + } + + public String getName() { + return mName; + } + + /** + * Populate name from BluetoothDevice.ACTION_FOUND intent + */ + void setNewName(String name) { + if (mName == null) { + mName = name; + if (mName == null || TextUtils.isEmpty(mName)) { + mName = mDevice.getAddress(); + } + dispatchAttributesChanged(); + } + } + + /** + * user changes the device name + */ + public void setName(String name) { + if (!mName.equals(name)) { + mName = name; + mDevice.setAlias(name); + dispatchAttributesChanged(); + } + } + + void refreshName() { + fetchName(); + dispatchAttributesChanged(); + } + + private void fetchName() { + mName = mDevice.getAliasName(); + + if (TextUtils.isEmpty(mName)) { + mName = mDevice.getAddress(); + if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName); + } + } + + void refresh() { + dispatchAttributesChanged(); + } + + public boolean isVisible() { + return mVisible; + } + + public void setVisible(boolean visible) { + if (mVisible != visible) { + mVisible = visible; + dispatchAttributesChanged(); + } + } + + public int getBondState() { + return mDevice.getBondState(); + } + + void setRssi(short rssi) { + if (mRssi != rssi) { + mRssi = rssi; + dispatchAttributesChanged(); + } + } + + /** + * Checks whether we are connected to this device (any profile counts). + * + * @return Whether it is connected. + */ + public boolean isConnected() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTED) { + return true; + } + } + + return false; + } + + public boolean isConnectedProfile(LocalBluetoothProfile profile) { + int status = getProfileConnectionState(profile); + return status == BluetoothProfile.STATE_CONNECTED; + + } + + public boolean isBusy() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTING + || status == BluetoothProfile.STATE_DISCONNECTING) { + return true; + } + } + return getBondState() == BluetoothDevice.BOND_BONDING; + } + + /** + * Fetches a new value for the cached BT class. + */ + private void fetchBtClass() { + mBtClass = mDevice.getBluetoothClass(); + } + + private boolean updateProfiles() { + ParcelUuid[] uuids = mDevice.getUuids(); + if (uuids == null) return false; + + ParcelUuid[] localUuids = mLocalAdapter.getUuids(); + if (localUuids == null) return false; + + /** + * Now we know if the device supports PBAP, update permissions... + */ + processPhonebookAccess(); + + mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles, + mLocalNapRoleConnected, mDevice); + + if (DEBUG) { + Log.e(TAG, "updating profiles for " + mDevice.getAliasName()); + BluetoothClass bluetoothClass = mDevice.getBluetoothClass(); + + if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); + Log.v(TAG, "UUID:"); + for (ParcelUuid uuid : uuids) { + Log.v(TAG, " " + uuid); + } + } + return true; + } + + /** + * Refreshes the UI for the BT class, including fetching the latest value + * for the class. + */ + void refreshBtClass() { + fetchBtClass(); + dispatchAttributesChanged(); + } + + /** + * Refreshes the UI when framework alerts us of a UUID change. + */ + void onUuidChanged() { + updateProfiles(); + + if (DEBUG) { + Log.e(TAG, "onUuidChanged: Time since last connect" + + (SystemClock.elapsedRealtime() - mConnectAttempted)); + } + + /* + * If a connect was attempted earlier without any UUID, we will do the + * connect now. + */ + if (!mProfiles.isEmpty() + && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock + .elapsedRealtime()) { + connectWithoutResettingTimer(false); + } + dispatchAttributesChanged(); + } + + void onBondingStateChanged(int bondState) { + if (bondState == BluetoothDevice.BOND_NONE) { + mProfiles.clear(); + mConnectAfterPairing = false; // cancel auto-connect + setPhonebookPermissionChoice(ACCESS_UNKNOWN); + setMessagePermissionChoice(ACCESS_UNKNOWN); + mMessageRejectionCount = 0; + saveMessageRejectionCount(); + } + + refresh(); + + if (bondState == BluetoothDevice.BOND_BONDED) { + if (mDevice.isBluetoothDock()) { + onBondingDockConnect(); + } else if (mConnectAfterPairing) { + connect(false); + } + mConnectAfterPairing = false; + } + } + + void setBtClass(BluetoothClass btClass) { + if (btClass != null && mBtClass != btClass) { + mBtClass = btClass; + dispatchAttributesChanged(); + } + } + + public BluetoothClass getBtClass() { + return mBtClass; + } + + public List<LocalBluetoothProfile> getProfiles() { + return Collections.unmodifiableList(mProfiles); + } + + public List<LocalBluetoothProfile> getConnectableProfiles() { + List<LocalBluetoothProfile> connectableProfiles = + new ArrayList<LocalBluetoothProfile>(); + for (LocalBluetoothProfile profile : mProfiles) { + if (profile.isConnectable()) { + connectableProfiles.add(profile); + } + } + return connectableProfiles; + } + + public List<LocalBluetoothProfile> getRemovedProfiles() { + return mRemovedProfiles; + } + + public void registerCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.add(callback); + } + } + + public void unregisterCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + private void dispatchAttributesChanged() { + synchronized (mCallbacks) { + for (Callback callback : mCallbacks) { + callback.onDeviceAttributesChanged(); + } + } + } + + @Override + public String toString() { + return mDevice.toString(); + } + + @Override + public boolean equals(Object o) { + if ((o == null) || !(o instanceof CachedBluetoothDevice)) { + return false; + } + return mDevice.equals(((CachedBluetoothDevice) o).mDevice); + } + + @Override + public int hashCode() { + 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) { + // Connected above not connected + int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); + if (comparison != 0) return comparison; + + // Paired above not paired + comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) - + (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); + if (comparison != 0) return comparison; + + // Visible above not visible + comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0); + if (comparison != 0) return comparison; + + // Stronger signal above weaker signal + comparison = another.mRssi - mRssi; + if (comparison != 0) return comparison; + + // Fallback on name + return mName.compareTo(another.mName); + } + + public interface Callback { + void onDeviceAttributesChanged(); + } + + public int getPhonebookPermissionChoice() { + int permission = mDevice.getPhonebookAccessPermission(); + if (permission == BluetoothDevice.ACCESS_ALLOWED) { + return ACCESS_ALLOWED; + } else if (permission == BluetoothDevice.ACCESS_REJECTED) { + return ACCESS_REJECTED; + } + return ACCESS_UNKNOWN; + } + + public void setPhonebookPermissionChoice(int permissionChoice) { + int permission = BluetoothDevice.ACCESS_UNKNOWN; + if (permissionChoice == ACCESS_ALLOWED) { + permission = BluetoothDevice.ACCESS_ALLOWED; + } else if (permissionChoice == ACCESS_REJECTED) { + permission = BluetoothDevice.ACCESS_REJECTED; + } + mDevice.setPhonebookAccessPermission(permission); + } + + // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth + // app's shared preferences). + private void migratePhonebookPermissionChoice() { + SharedPreferences preferences = mContext.getSharedPreferences( + "bluetooth_phonebook_permission", Context.MODE_PRIVATE); + if (!preferences.contains(mDevice.getAddress())) { + return; + } + + if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { + int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN); + if (oldPermission == ACCESS_ALLOWED) { + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); + } else if (oldPermission == ACCESS_REJECTED) { + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); + } + } + + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(mDevice.getAddress()); + editor.commit(); + } + + public int getMessagePermissionChoice() { + int permission = mDevice.getMessageAccessPermission(); + if (permission == BluetoothDevice.ACCESS_ALLOWED) { + return ACCESS_ALLOWED; + } else if (permission == BluetoothDevice.ACCESS_REJECTED) { + return ACCESS_REJECTED; + } + return ACCESS_UNKNOWN; + } + + public void setMessagePermissionChoice(int permissionChoice) { + int permission = BluetoothDevice.ACCESS_UNKNOWN; + if (permissionChoice == ACCESS_ALLOWED) { + permission = BluetoothDevice.ACCESS_ALLOWED; + } else if (permissionChoice == ACCESS_REJECTED) { + permission = BluetoothDevice.ACCESS_REJECTED; + } + mDevice.setMessageAccessPermission(permission); + } + + // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth + // app's shared preferences). + private void migrateMessagePermissionChoice() { + SharedPreferences preferences = mContext.getSharedPreferences( + "bluetooth_message_permission", Context.MODE_PRIVATE); + if (!preferences.contains(mDevice.getAddress())) { + return; + } + + if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { + int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN); + if (oldPermission == ACCESS_ALLOWED) { + mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED); + } else if (oldPermission == ACCESS_REJECTED) { + mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); + } + } + + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(mDevice.getAddress()); + editor.commit(); + } + + /** + * @return Whether this rejection should persist. + */ + public boolean checkAndIncreaseMessageRejectionCount() { + if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) { + mMessageRejectionCount++; + saveMessageRejectionCount(); + } + return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST; + } + + private void fetchMessageRejectionCount() { + SharedPreferences preference = mContext.getSharedPreferences( + MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE); + mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0); + } + + private void saveMessageRejectionCount() { + SharedPreferences.Editor editor = mContext.getSharedPreferences( + MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit(); + if (mMessageRejectionCount == 0) { + editor.remove(mDevice.getAddress()); + } else { + editor.putInt(mDevice.getAddress(), mMessageRejectionCount); + } + editor.commit(); + } + + private void processPhonebookAccess() { + if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return; + + ParcelUuid[] uuids = mDevice.getUuids(); + if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) { + // The pairing dialog now warns of phone-book access for paired devices. + // No separate prompt is displayed after pairing. + setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java new file mode 100755 index 0000000..65db95f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -0,0 +1,172 @@ +/* + * 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. + */ +public final class CachedBluetoothDeviceManager { + private static final String TAG = "CachedBluetoothDeviceManager"; + private static final boolean DEBUG = Utils.D; + + private Context mContext; + private final List<CachedBluetoothDevice> mCachedDevices = + new ArrayList<CachedBluetoothDevice>(); + + CachedBluetoothDeviceManager(Context context) { + mContext = context; + } + + public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() { + return new ArrayList<CachedBluetoothDevice>(mCachedDevices); + } + + public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) { + cachedDevice.setVisible(false); + return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE; + } + + public void onDeviceNameUpdated(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.refreshName(); + } + } + + /** + * 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 + */ + public 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 + */ + public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter, + profileManager, device); + synchronized (mCachedDevices) { + mCachedDevices.add(newDevice); + } + return newDevice; + } + + /** + * Attempts to get the name of a remote device, otherwise returns the address. + * + * @param device The remote device. + * @return The name, or if unavailable, the address. + */ + public String getName(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + return cachedDevice.getName(); + } + + String name = device.getAliasName(); + if (name != null) { + return name; + } + + return device.getAddress(); + } + + public synchronized void clearNonBondedDevices() { + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + mCachedDevices.remove(i); + } + } + } + + 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); + } + } + + public synchronized void onBtClassChanged(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.refreshBtClass(); + } + } + + public synchronized void onUuidChanged(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.onUuidChanged(); + } + } + + public synchronized void onBluetoothStateChanged(int bluetoothState) { + // When Bluetooth is turning off, we need to clear the non-bonded devices + // Otherwise, they end up showing up on the next BT enable + if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + cachedDevice.setVisible(false); + mCachedDevices.remove(i); + } else { + // For bonded devices, we need to clear the connection status so that + // when BT is enabled next time, device connection status shall be retrieved + // by making a binder call. + cachedDevice.clearProfileConnectionState(); + } + } + } + } + private void log(String msg) { + if (DEBUG) { + Log.d(TAG, msg); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java new file mode 100755 index 0000000..5529866 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +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.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * HeadsetProfile handles Bluetooth HFP and Headset profiles. + */ +public final class HeadsetProfile implements LocalBluetoothProfile { + private static final String TAG = "HeadsetProfile"; + private static boolean V = true; + + private BluetoothHeadset mService; + private boolean mIsProfileReady; + + 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) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothHeadset) proxy; + // We just bound to the service, so refresh the UI for any connected HFP devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "HeadsetProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(HeadsetProfile.this, + BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mProfileManager.callServiceDisconnectedListeners(); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + 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) { + if (mService == null) return false; + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + Log.d(TAG,"Not disconnecting device = " + sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty()) { + for (BluetoothDevice dev : deviceList) { + if (dev.equals(device)) { + if (V) Log.d(TAG,"Downgrade priority as user" + + "is disconnecting the headset"); + // 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); + } + } + } + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty()){ + for (BluetoothDevice dev : deviceList) { + if (dev.equals(device)) { + return mService.getConnectionState(device); + } + } + } + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public List<BluetoothDevice> getConnectedDevices() { + if (mService == null) return new ArrayList<BluetoothDevice>(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_headset; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(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; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEADSET, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up HID proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java new file mode 100755 index 0000000..a9e8db5 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2012 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.List; + +/** + * HidProfile handles Bluetooth HID profile. + */ +public final class HidProfile implements LocalBluetoothProfile { + private static final String TAG = "HidProfile"; + private static boolean V = true; + + private BluetoothInputDevice mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + 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) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothInputDevice) proxy; + // We just bound to the service, so refresh the UI for any connected HID devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "HidProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(HidProfile.this, BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + HidProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + adapter.getProfileProxy(context, new InputDeviceServiceListener(), + BluetoothProfile.INPUT_DEVICE); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + // TODO: distinguish between keyboard and mouse? + return R.string.bluetooth_profile_hid; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(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_lockscreen_ime; + } + return getHidClassDrawable(btClass); + } + + public static int getHidClassDrawable(BluetoothClass btClass) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.PERIPHERAL_KEYBOARD: + case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING: + return R.drawable.ic_lockscreen_ime; + case BluetoothClass.Device.PERIPHERAL_POINTING: + return R.drawable.ic_bt_pointing_hid; + default: + return R.drawable.ic_bt_misc_hid; + } + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up HID proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java new file mode 100644 index 0000000..0c1adec --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/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.settingslib.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 + + public void cancelDiscovery() { + mAdapter.cancelDiscovery(); + } + + public boolean enable() { + return mAdapter.enable(); + } + + public boolean disable() { + return mAdapter.disable(); + } + + void getProfileProxy(Context context, + BluetoothProfile.ServiceListener listener, int profile) { + mAdapter.getProfileProxy(context, listener, profile); + } + + public Set<BluetoothDevice> getBondedDevices() { + return mAdapter.getBondedDevices(); + } + + public String getName() { + return mAdapter.getName(); + } + + public int getScanMode() { + return mAdapter.getScanMode(); + } + + public int getState() { + return mAdapter.getState(); + } + + public ParcelUuid[] getUuids() { + return mAdapter.getUuids(); + } + + public boolean isDiscovering() { + return mAdapter.isDiscovering(); + } + + public boolean isEnabled() { + return mAdapter.isEnabled(); + } + + public void setDiscoverableTimeout(int timeout) { + mAdapter.setDiscoverableTimeout(timeout); + } + + public void setName(String name) { + mAdapter.setName(name); + } + + public void setScanMode(int mode) { + mAdapter.setScanMode(mode); + } + + public boolean setScanMode(int mode, int duration) { + return mAdapter.setScanMode(mode, duration); + } + + public 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(); + } + } + } + + public 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/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java new file mode 100644 index 0000000..4adc62e --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java @@ -0,0 +1,122 @@ +/* + * 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.settingslib.bluetooth; + +import android.content.Context; +import android.util.Log; + +/** + * LocalBluetoothManager provides a simplified interface on top of a subset of + * 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 final class LocalBluetoothManager { + private static final String TAG = "LocalBluetoothManager"; + + /** Singleton instance. */ + private static LocalBluetoothManager sInstance; + + private final Context mContext; + + /** If a BT-related activity is in the foreground, this will be it. */ + private Context mForegroundActivity; + + private final LocalBluetoothAdapter mLocalAdapter; + + private final CachedBluetoothDeviceManager mCachedDeviceManager; + + /** The Bluetooth profile manager. */ + private final LocalBluetoothProfileManager mProfileManager; + + /** The broadcast receiver event manager. */ + private final BluetoothEventManager mEventManager; + + public static synchronized LocalBluetoothManager getInstance(Context context, + BluetoothManagerCallback onInitCallback) { + if (sInstance == null) { + LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance(); + if (adapter == null) { + return null; + } + // This will be around as long as this process is + Context appContext = context.getApplicationContext(); + sInstance = new LocalBluetoothManager(adapter, appContext); + if (onInitCallback != null) { + onInitCallback.onBluetoothManagerInitialized(appContext, sInstance); + } + } + + return sInstance; + } + + private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) { + mContext = context; + mLocalAdapter = adapter; + + mCachedDeviceManager = new CachedBluetoothDeviceManager(context); + mEventManager = new BluetoothEventManager(mLocalAdapter, + mCachedDeviceManager, context); + mProfileManager = new LocalBluetoothProfileManager(context, + mLocalAdapter, mCachedDeviceManager, mEventManager); + } + + public LocalBluetoothAdapter getBluetoothAdapter() { + return mLocalAdapter; + } + + public Context getContext() { + return mContext; + } + + public Context getForegroundActivity() { + return mForegroundActivity; + } + + public boolean isForegroundActivity() { + return mForegroundActivity != null; + } + + public synchronized void setForegroundActivity(Context context) { + if (context != null) { + Log.d(TAG, "setting foreground activity to non-null context"); + mForegroundActivity = context; + } else { + if (mForegroundActivity != null) { + Log.d(TAG, "setting foreground activity to null"); + mForegroundActivity = null; + } + } + } + + public CachedBluetoothDeviceManager getCachedDeviceManager() { + return mCachedDeviceManager; + } + + public BluetoothEventManager getEventManager() { + return mEventManager; + } + + public LocalBluetoothProfileManager getProfileManager() { + return mProfileManager; + } + + public interface BluetoothManagerCallback { + void onBluetoothManagerInitialized(Context appContext, + LocalBluetoothManager bluetoothManager); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java new file mode 100755 index 0000000..abcb989 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java @@ -0,0 +1,71 @@ +/* + * 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; + +/** + * LocalBluetoothProfile is an interface defining the basic + * functionality related to a Bluetooth profile. + */ +public 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. + * @param device the Bluetooth device (to distinguish between PAN roles) + */ + int getNameResource(BluetoothDevice device); + + /** + * 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/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java new file mode 100644 index 0000000..b0a7b27 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -0,0 +1,383 @@ +/* + * 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothMap; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothPbap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.content.Intent; +import android.os.ParcelUuid; +import android.util.Log; +import android.os.Handler; +import android.os.Message; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.List; + +/** + * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile + * objects for the available Bluetooth profiles. + */ +public final class LocalBluetoothProfileManager { + private static final String TAG = "LocalBluetoothProfileManager"; + private static final boolean DEBUG = Utils.D; + /** 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 com.android.settings.bluetooth.DockService. + */ + public interface ServiceListener { + /** + * Called to notify the client when this proxy object has been + * connected to the BluetoothHeadset service. Clients must wait for + * this callback before making IPC calls on the BluetoothHeadset + * service. + */ + void onServiceConnected(); + + /** + * Called to notify the client that this proxy object has been + * disconnected from the BluetoothHeadset service. Clients must not + * make IPC calls on the BluetoothHeadset service after this callback. + * This callback will currently only occur if the application hosting + * the BluetoothHeadset service, but may be called more often in future. + */ + void onServiceDisconnected(); + } + + private final Context mContext; + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final BluetoothEventManager mEventManager; + + private A2dpProfile mA2dpProfile; + private HeadsetProfile mHeadsetProfile; + private MapProfile mMapProfile; + private final HidProfile mHidProfile; + private OppProfile mOppProfile; + private final PanProfile mPanProfile; + private final PbapServerProfile mPbapProfile; + + /** + * Mapping from profile name, e.g. "HEADSET" to profile object. + */ + private final Map<String, LocalBluetoothProfile> + mProfileNameMap = new HashMap<String, LocalBluetoothProfile>(); + + 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); + + ParcelUuid[] uuids = adapter.getUuids(); + + // uuids may be null if Bluetooth is turned off + if (uuids != null) { + updateLocalProfiles(uuids); + } + + // Always add HID and PAN profiles + mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this); + addProfile(mHidProfile, HidProfile.NAME, + BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); + + mPanProfile = new PanProfile(context); + addPanProfile(mPanProfile, PanProfile.NAME, + BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); + + if(DEBUG) Log.d(TAG, "Adding local MAP profile"); + mMapProfile = new MapProfile(mContext, mLocalAdapter, + mDeviceManager, this); + addProfile(mMapProfile, MapProfile.NAME, + BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); + + //Create PBAP server profile, but do not add it to list of profiles + // as we do not need to monitor the profile as part of profile list + mPbapProfile = new PbapServerProfile(context); + + if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete"); + } + + /** + * 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 + */ + void updateLocalProfiles(ParcelUuid[] uuids) { + // A2DP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) { + if (mA2dpProfile == null) { + if(DEBUG) Log.d(TAG, "Adding local A2DP profile"); + mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this); + addProfile(mA2dpProfile, A2dpProfile.NAME, + BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + } + } 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) { + if (DEBUG) Log.d(TAG, "Adding local HEADSET profile"); + mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter, + mDeviceManager, this); + addProfile(mHeadsetProfile, HeadsetProfile.NAME, + BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + } + } else if (mHeadsetProfile != null) { + Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing."); + } + + // OPP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + if (mOppProfile == null) { + if(DEBUG) 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."); + } + mEventManager.registerProfileIntentReceiver(); + + // There is no local SDP record for HID and Settings app doesn't control PBAP + } + + private final Collection<ServiceListener> mServiceListeners = + new ArrayList<ServiceListener>(); + + private void addProfile(LocalBluetoothProfile profile, + String profileName, String stateChangedAction) { + mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile)); + mProfileNameMap.put(profileName, profile); + } + + private void addPanProfile(LocalBluetoothProfile profile, + String profileName, String stateChangedAction) { + mEventManager.addProfileHandler(stateChangedAction, + new PanStateChangedHandler(profile)); + mProfileNameMap.put(profileName, profile); + } + + public LocalBluetoothProfile getProfileByName(String name) { + return mProfileNameMap.get(name); + } + + // Called from LocalBluetoothAdapter when state changes to ON + void setBluetoothStateOn() { + ParcelUuid[] uuids = mLocalAdapter.getUuids(); + if (uuids != null) { + updateLocalProfiles(uuids); + } + mEventManager.readPairedDevices(); + } + + /** + * Generic handler for connection state change events for the specified profile. + */ + private class StateChangedHandler implements BluetoothEventManager.Handler { + final LocalBluetoothProfile mProfile; + + StateChangedHandler(LocalBluetoothProfile profile) { + mProfile = profile; + } + + 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); + } + 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"); + } + + cachedDevice.onProfileStateChanged(mProfile, newState); + cachedDevice.refresh(); + } + } + + /** State change handler for NAP and PANU profiles. */ + private class PanStateChangedHandler extends StateChangedHandler { + + PanStateChangedHandler(LocalBluetoothProfile profile) { + super(profile); + } + + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + PanProfile panProfile = (PanProfile) mProfile; + int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0); + panProfile.setLocalRole(device, role); + super.onReceive(context, intent, device); + } + } + + // called from DockService + public void addServiceListener(ServiceListener l) { + mServiceListeners.add(l); + } + + // called from DockService + public void removeServiceListener(ServiceListener l) { + mServiceListeners.remove(l); + } + + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceConnectedListeners() { + for (ServiceListener l : mServiceListeners) { + l.onServiceConnected(); + } + } + + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceDisconnectedListeners() { + for (ServiceListener listener : mServiceListeners) { + listener.onServiceDisconnected(); + } + } + + // 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(); + } + profile = mA2dpProfile; + if (profile != null) { + return profile.isProfileReady(); + } + return false; + } + + public A2dpProfile getA2dpProfile() { + return mA2dpProfile; + } + + public HeadsetProfile getHeadsetProfile() { + return mHeadsetProfile; + } + + public PbapServerProfile getPbapProfile(){ + return mPbapProfile; + } + + public MapProfile getMapProfile(){ + return mMapProfile; + } + + /** + * 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 + * @param removedProfiles list of profiles that were removed + */ + synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, + Collection<LocalBluetoothProfile> profiles, + Collection<LocalBluetoothProfile> removedProfiles, + boolean isPanNapConnected, BluetoothDevice device) { + // Copy previous profile list into removedProfiles + removedProfiles.clear(); + removedProfiles.addAll(profiles); + profiles.clear(); + + if (uuids == null) { + return; + } + + 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); + removedProfiles.remove(mHeadsetProfile); + } + } + + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && + mA2dpProfile != null) { + profiles.add(mA2dpProfile); + removedProfiles.remove(mA2dpProfile); + } + + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) && + mOppProfile != null) { + profiles.add(mOppProfile); + removedProfiles.remove(mOppProfile); + } + + if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) || + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) && + mHidProfile != null) { + profiles.add(mHidProfile); + removedProfiles.remove(mHidProfile); + } + + if(isPanNapConnected) + if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists."); + if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) && + mPanProfile != null) || isPanNapConnected) { + profiles.add(mPanProfile); + removedProfiles.remove(mPanProfile); + } + + if ((mMapProfile != null) && + (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { + profiles.add(mMapProfile); + removedProfiles.remove(mMapProfile); + mMapProfile.setPreferred(device, true); + } + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java new file mode 100644 index 0000000..e6a152f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothMap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * MapProfile handles Bluetooth MAP profile. + */ +public final class MapProfile implements LocalBluetoothProfile { + private static final String TAG = "MapProfile"; + private static boolean V = true; + + private BluetoothMap mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final ParcelUuid[] UUIDS = { + BluetoothUuid.MAP, + BluetoothUuid.MNS, + BluetoothUuid.MAS, + }; + + static final String NAME = "MAP"; + + // Order of this profile in device profiles list + + // These callbacks run on the main thread. + private final class MapServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothMap) proxy; + // We just bound to the service, so refresh the UI for any connected MAP devices. + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "MapProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(MapProfile.this, + BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mProfileManager.callServiceDisconnectedListeners(); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + if(V) Log.d(TAG,"isProfileReady(): "+ mIsProfileReady); + return mIsProfileReady; + } + + MapProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new MapServiceListener(), + BluetoothProfile.MAP); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + if(V)Log.d(TAG,"connect() - should not get called"); + return false; // MAP never connects out + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) { + 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) { + if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; + List<BluetoothDevice> deviceList = mService.getConnectedDevices(); + if(V) Log.d(TAG,"getConnectionStatus: status is: "+ mService.getConnectionState(device)); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public List<BluetoothDevice> getConnectedDevices() { + if (mService == null) return new ArrayList<BluetoothDevice>(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return BluetoothProfile.MAP; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_map; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_map_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_map_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_cellphone; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.MAP, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up MAP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java new file mode 100755 index 0000000..31e675c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java @@ -0,0 +1,89 @@ +/* + * 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.settingslib.bluetooth; + +import com.android.settingslib.R; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; + +/** + * 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 BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP + } + + public boolean isPreferred(BluetoothDevice device) { + return false; + } + + public int getPreferred(BluetoothDevice device) { + return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP + } + + 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(BluetoothDevice device) { + return R.string.bluetooth_profile_opp; + } + + 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/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java new file mode 100755 index 0000000..3af89e6 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -0,0 +1,183 @@ +/* + * 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.settingslib.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 android.util.Log; + +import com.android.settingslib.R; + +import java.util.HashMap; +import java.util.List; + +/** + * PanProfile handles Bluetooth PAN profile (NAP and PANU). + */ +final class PanProfile implements LocalBluetoothProfile { + private static final String TAG = "PanProfile"; + private static boolean V = true; + + private BluetoothPan mService; + private boolean mIsProfileReady; + + // Tethering direction for each device + private final HashMap<BluetoothDevice, Integer> mDeviceRoleMap = + new HashMap<BluetoothDevice, Integer>(); + + 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) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothPan) proxy; + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + 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) { + if (mService == null) return false; + List<BluetoothDevice> sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + // return current connection status so profile checkbox is set correctly + return getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED; + } + + public int getPreferred(BluetoothDevice device) { + return -1; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + // ignore: isPreferred is always true for PAN + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + if (isLocalRoleNap(device)) { + return R.string.bluetooth_profile_pan_nap; + } else { + return R.string.bluetooth_profile_pan; + } + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_pan_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + if (isLocalRoleNap(device)) { + return R.string.bluetooth_pan_nap_profile_summary_connected; + } else { + return R.string.bluetooth_pan_user_profile_summary_connected; + } + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_network_pan; + } + + // Tethering direction determines UI strings. + void setLocalRole(BluetoothDevice device, int role) { + mDeviceRoleMap.put(device, role); + } + + boolean isLocalRoleNap(BluetoothDevice device) { + if (mDeviceRoleMap.containsKey(device)) { + return mDeviceRoleMap.get(device) == BluetoothPan.LOCAL_NAP_ROLE; + } else { + return false; + } + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PAN, mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up PAN proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java new file mode 100755 index 0000000..a552b24a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -0,0 +1,152 @@ +/* + * 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.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothPbap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.HashMap; +import java.util.List; + +/** + * PBAPServer Profile + */ +public final class PbapServerProfile implements LocalBluetoothProfile { + private static final String TAG = "PbapServerProfile"; + private static boolean V = true; + + private BluetoothPbap mService; + private boolean mIsProfileReady; + + static final String NAME = "PBAP Server"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 6; + + // The UUIDs indicate that remote device might access pbap server + static final ParcelUuid[] PBAB_CLIENT_UUIDS = { + BluetoothUuid.HSP, + BluetoothUuid.Handsfree, + BluetoothUuid.PBAP_PCE + }; + + // These callbacks run on the main thread. + private final class PbapServiceListener + implements BluetoothPbap.ServiceListener { + + public void onServiceConnected(BluetoothPbap proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothPbap) proxy; + mIsProfileReady=true; + } + + public void onServiceDisconnected() { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + PbapServerProfile(Context context) { + BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener()); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + /*Can't connect from server */ + return false; + + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + if (mService.isConnected(device)) + return BluetoothProfile.STATE_CONNECTED; + else + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + return false; + } + + public int getPreferred(BluetoothDevice device) { + return -1; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + // ignore: isPreferred is always true for PBAP + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_pbap; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return R.string.bluetooth_profile_pbap_summary; + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_cellphone; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + mService.close(); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up PBAP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java new file mode 100644 index 0000000..c919426 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java @@ -0,0 +1,43 @@ +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settingslib.R; + +public class Utils { + public static final boolean V = false; // verbose logging + public static final boolean D = true; // regular logging + + private static ErrorListener sErrorListener; + + 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; + } + } + + static void showError(Context context, String name, int messageResId) { + if (sErrorListener != null) { + sErrorListener.onShowError(context, name, messageResId); + } + } + + public static void setErrorListener(ErrorListener listener) { + sErrorListener = listener; + } + + public interface ErrorListener { + void onShowError(Context context, String name, int messageResId); + } + +} |