diff options
Diffstat (limited to 'packages/SettingsLib')
70 files changed, 5947 insertions, 0 deletions
diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk new file mode 100644 index 0000000..0245ed3 --- /dev/null +++ b/packages/SettingsLib/Android.mk @@ -0,0 +1,9 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := SettingsLib + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml new file mode 100644 index 0000000..eacafd5 --- /dev/null +++ b/packages/SettingsLib/AndroidManifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2015 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib"> +</manifest> diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk new file mode 100644 index 0000000..b017047 --- /dev/null +++ b/packages/SettingsLib/common.mk @@ -0,0 +1,18 @@ +# +# Include this make file to build your application against this module. +# +# Make sure to include it after you've set all your desired LOCAL variables. +# Note that you must explicitly set your LOCAL_RESOURCE_DIR before including +# this file. +# +# For example: +# +# LOCAL_RESOURCE_DIR := \ +# $(LOCAL_PATH)/res +# +# include frameworks/base/packages/SettingsLib/common.mk +# + +LOCAL_RESOURCE_DIR += $(call my-dir)/res +LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages com.android.settingslib +LOCAL_STATIC_JAVA_LIBRARIES += SettingsLib 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/arrays.xml b/packages/SettingsLib/res/values/arrays.xml new file mode 100644 index 0000000..a52ed69 --- /dev/null +++ b/packages/SettingsLib/res/values/arrays.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2015 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. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Wi-Fi settings --> + + <!-- Match this with the order of NetworkInfo.DetailedState. --> <skip /> + <!-- Wi-Fi settings. The status messages when the network is unknown. --> + <string-array name="wifi_status"> + <!-- Status message of Wi-Fi when it is idle. --> + <item></item> + <!-- Status message of Wi-Fi when it is scanning. --> + <item>Scanning\u2026</item> + <!-- Status message of Wi-Fi when it is connecting. --> + <item>Connecting\u2026</item> + <!-- Status message of Wi-Fi when it is authenticating. --> + <item>Authenticating\u2026</item> + <!-- Status message of Wi-Fi when it is obtaining IP address. --> + <item>Obtaining IP address\u2026</item> + <!-- Status message of Wi-Fi when it is connected. --> + <item>Connected</item> + <!-- Status message of Wi-Fi when it is suspended. --> + <item>Suspended</item> + <!-- Status message of Wi-Fi when it is disconnecting. --> + <item>Disconnecting\u2026</item> + <!-- Status message of Wi-Fi when it is disconnected. --> + <item>Disconnected</item> + <!-- Status message of Wi-Fi when it is a failure. --> + <item>Unsuccessful</item> + <!-- Status message of Wi-Fi when it is blocked. --> + <item>Blocked</item> + <!-- Status message of Wi-Fi when connectiong is being verified. --> + <item>Temporarily avoiding poor connection</item> + </string-array> + + <!-- Match this with the order of NetworkInfo.DetailedState. --> <skip /> + <!-- Wi-Fi settings. The status messages when the network is known. --> + <string-array name="wifi_status_with_ssid"> + <!-- Status message of Wi-Fi when it is idle. --> + <item></item> + <!-- Status message of Wi-Fi when it is scanning. --> + <item>Scanning\u2026</item> + <!-- Status message of Wi-Fi when it is connecting to a network. --> + <item>Connecting to <xliff:g id="network_name">%1$s</xliff:g>\u2026</item> + <!-- Status message of Wi-Fi when it is authenticating with a network. --> + <item>Authenticating with <xliff:g id="network_name">%1$s</xliff:g>\u2026</item> + <!-- Status message of Wi-Fi when it is obtaining IP address from a network. --> + <item>Obtaining IP address from <xliff:g id="network_name">%1$s</xliff:g>\u2026</item> + <!-- Status message of Wi-Fi when it is connected to a network. --> + <item>Connected to <xliff:g id="network_name">%1$s</xliff:g></item> + <!-- Status message of Wi-Fi when it is suspended. --> + <item>Suspended</item> + <!-- Status message of Wi-Fi when it is disconnecting from a network. --> + <item>Disconnecting from <xliff:g id="network_name">%1$s</xliff:g>\u2026</item> + <!-- Status message of Wi-Fi when it is disconnected. --> + <item>Disconnected</item> + <!-- Status message of Wi-Fi when it is a failure. --> + <item>Unsuccessful</item> + <!-- Status message of Wi-Fi when it is blocked. --> + <item>Blocked</item> + <!-- Status message of Wi-Fi when connectiong is being verified. --> + <item>Temporarily avoiding poor connection</item> + </string-array> +</resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml new file mode 100644 index 0000000..b5e49ce --- /dev/null +++ b/packages/SettingsLib/res/values/strings.xml @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2015 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. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Toast message when Wi-Fi cannot scan for networks --> + <string name="wifi_fail_to_scan">Can\'t scan for networks</string> + <!-- Do not translate. Concise terminology for wifi with WEP security --> + <string name="wifi_security_short_wep">WEP</string> + <!-- Do not translate. Concise terminology for wifi with WPA security --> + <string name="wifi_security_short_wpa">WPA</string> + <!-- Do not translate. Concise terminology for wifi with WPA2 security --> + <string name="wifi_security_short_wpa2">WPA2</string> + <!-- Do not translate. Concise terminology for wifi with both WPA/WPA2 security --> + <string name="wifi_security_short_wpa_wpa2">WPA/WPA2</string> + <!-- Do not translate. Concise terminology for wifi with unknown PSK type --> + <string name="wifi_security_short_psk_generic">@string/wifi_security_short_wpa_wpa2</string> + <!-- Do not translate. Concise terminology for wifi with 802.1x EAP security --> + <string name="wifi_security_short_eap">802.1x</string> + + <!-- Used in Wi-Fi settings dialogs when Wi-Fi does not have any security. --> + <string name="wifi_security_none">None</string> + + <!-- Do not translate. Terminology for wifi with WEP security --> + <string name="wifi_security_wep">WEP</string> + <!-- Do not translate. Terminology for wifi with WPA security --> + <string name="wifi_security_wpa">WPA PSK</string> + <!-- Do not translate. Terminology for wifi with WPA2 security --> + <string name="wifi_security_wpa2">WPA2 PSK</string> + <!-- Do not translate. Terminology for wifi with both WPA/WPA2 security, or unknown --> + <string name="wifi_security_wpa_wpa2">WPA/WPA2 PSK</string> + <!-- Do not translate. Terminology for wifi with unknown PSK type --> + <string name="wifi_security_psk_generic">@string/wifi_security_wpa_wpa2</string> + <!-- Do not translate. Concise terminology for wifi with 802.1x EAP security --> + <string name="wifi_security_eap">802.1x EAP</string> + + <!-- Summary for the remembered network. --> + <string name="wifi_remembered">Saved</string> + <!-- Status for networks disabled for unknown reason --> + <string name="wifi_disabled_generic">Disabled</string> + <!-- Status for networked disabled from a DNS or DHCP failure --> + <string name="wifi_disabled_network_failure">IP Configuration Failure</string> + <!-- Status for networked disabled from a wifi association failure --> + <string name="wifi_disabled_wifi_failure">WiFi Connection Failure</string> + <!-- Status for networks disabled from authentication failure (wrong password + or certificate). --> + <string name="wifi_disabled_password_failure">Authentication problem</string> + <!-- Summary for the remembered network but currently not in range. --> + <string name="wifi_not_in_range">Not in range</string> + <!-- Summary for the remembered network but no internet connection was detected. --> + <string name="wifi_no_internet">No Internet Access Detected, won\'t automatically reconnect.</string> + + <!-- 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 screen, summary text under individual Bluetooth devices when pairing --> + <string name="bluetooth_pairing">Pairing\u2026</string> + + <!-- Bluetooth settings. Message when connected to a device, except for phone audio. [CHAR LIMIT=40] --> + <string name="bluetooth_connected_no_headset">Connected (no phone)</string> + <!-- Bluetooth settings. Message when connected to a device, except for media audio. [CHAR LIMIT=40] --> + <string name="bluetooth_connected_no_a2dp">Connected (no media)</string> + <!-- Bluetooth settings. Message when connected to a device, except for map. [CHAR LIMIT=40] --> + <string name="bluetooth_connected_no_map">Connected (no message access)</string> + <!-- Bluetooth settings. Message when connected to a device, except for phone/media audio. [CHAR LIMIT=40] --> + <string name="bluetooth_connected_no_headset_no_a2dp">Connected (no phone or media)</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/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java new file mode 100644 index 0000000..58e5e29 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2015 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; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; + +public class TetherUtil { + + // Types of tethering. + public static final int TETHERING_INVALID = -1; + public static final int TETHERING_WIFI = 0; + public static final int TETHERING_USB = 1; + public static final int TETHERING_BLUETOOTH = 2; + + // Extras used for communicating with the TetherService. + public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; + public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; + public static final String EXTRA_SET_ALARM = "extraSetAlarm"; + /** + * Tells the service to run a provision check now. + */ + public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; + /** + * Enables wifi tethering if the provision check is successful. Used by + * QS to enable tethering. + */ + public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether"; + + public static ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources + .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable)); + + public static boolean setWifiTethering(boolean enable, Context context) { + final WifiManager wifiManager = + (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + final ContentResolver cr = context.getContentResolver(); + /** + * Disable Wifi if enabling tethering + */ + int wifiState = wifiManager.getWifiState(); + if (enable && ((wifiState == WifiManager.WIFI_STATE_ENABLING) || + (wifiState == WifiManager.WIFI_STATE_ENABLED))) { + wifiManager.setWifiEnabled(false); + Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1); + } + + boolean success = wifiManager.setWifiApEnabled(null, enable); + /** + * If needed, restore Wifi on tether disable + */ + if (!enable) { + int wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0); + if (wifiSavedState == 1) { + wifiManager.setWifiEnabled(true); + Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0); + } + } + return success; + } + + public static boolean isWifiTetherEnabled(Context context) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + return wifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED; + } + + public static boolean isProvisioningNeeded(Context context) { + // Keep in sync with other usage of config_mobile_hotspot_provision_app. + // ConnectivityManager#enforceTetherChangePermission + String[] provisionApp = context.getResources().getStringArray( + com.android.internal.R.array.config_mobile_hotspot_provision_app); + if (SystemProperties.getBoolean("net.tethering.noprovisioning", false) + || provisionApp == null) { + return false; + } + return (provisionApp.length == 2); + } + + public static boolean isTetheringSupported(Context context) { + final ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER; + return !isSecondaryUser && cm.isTetheringSupported(); + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/WirelessUtils.java b/packages/SettingsLib/src/com/android/settingslib/WirelessUtils.java new file mode 100644 index 0000000..0346a77 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/WirelessUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 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; + +import android.content.Context; +import android.provider.Settings; + +public class WirelessUtils { + + public static boolean isRadioAllowed(Context context, String type) { + if (!isAirplaneModeOn(context)) { + return true; + } + String toggleable = Settings.Global.getString(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + return toggleable != null && toggleable.contains(type); + } + + public static boolean isAirplaneModeOn(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } + +} 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..4c41b49 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -0,0 +1,31 @@ +/* + * 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); + void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state); +} 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..69b45e5 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java @@ -0,0 +1,85 @@ +/* + * 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.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..5d6b2f1 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -0,0 +1,381 @@ +/* + * 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()); + // Generic connected/not broadcast + addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED, + new ConnectionStateChangedHandler()); + + // 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); + } + cachedDevice.setRssi(rssi); + cachedDevice.setBtClass(btClass); + cachedDevice.setNewName(name); + cachedDevice.setVisible(true); + } + } + + private class ConnectionStateChangedHandler implements Handler { + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, + BluetoothAdapter.ERROR); + dispatchConnectionStateChanged(cachedDevice, state); + } + } + + private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onConnectionStateChanged(cachedDevice, state); + } + } + } + + 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..64b4452 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -0,0 +1,852 @@ +/* + * 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 com.android.settingslib.R; + +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; + private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000; + + /** 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(); + ParcelUuid[] uuids = mDevice.getUuids(); + long timeout = MAX_UUID_DELAY_FOR_AUTO_CONNECT; + + if (DEBUG) { + Log.d(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 (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) { + timeout = MAX_HOGP_DELAY_FOR_AUTO_CONNECT; + } + if (!mProfiles.isEmpty() + && (mConnectAttempted + timeout) > 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); + } + } + + public int getMaxConnectionState() { + int maxState = BluetoothProfile.STATE_DISCONNECTED; + for (LocalBluetoothProfile profile : getProfiles()) { + int connectionStatus = getProfileConnectionState(profile); + if (connectionStatus > maxState) { + maxState = connectionStatus; + } + } + return maxState; + } + + /** + * @return resource for string that discribes the connection state of this device. + */ + public int getConnectionSummary() { + boolean profileConnected = false; // at least one profile is connected + boolean a2dpNotConnected = false; // A2DP is preferred but not connected + boolean headsetNotConnected = false; // Headset is preferred but not connected + + for (LocalBluetoothProfile profile : getProfiles()) { + int connectionStatus = getProfileConnectionState(profile); + + switch (connectionStatus) { + case BluetoothProfile.STATE_CONNECTING: + case BluetoothProfile.STATE_DISCONNECTING: + return Utils.getConnectionStateSummary(connectionStatus); + + case BluetoothProfile.STATE_CONNECTED: + profileConnected = true; + break; + + case BluetoothProfile.STATE_DISCONNECTED: + if (profile.isProfileReady()) { + if (profile instanceof A2dpProfile) { + a2dpNotConnected = true; + } else if (profile instanceof HeadsetProfile) { + headsetNotConnected = true; + } + } + break; + } + } + + if (profileConnected) { + if (a2dpNotConnected && headsetNotConnected) { + return R.string.bluetooth_connected_no_headset_no_a2dp; + } else if (a2dpNotConnected) { + return R.string.bluetooth_connected_no_a2dp; + } else if (headsetNotConnected) { + return R.string.bluetooth_connected_no_headset; + } else { + return R.string.bluetooth_connected; + } + } + + return getBondState() == BluetoothDevice.BOND_BONDING ? R.string.bluetooth_pairing : 0; + } +} 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..a9f4bd3 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -0,0 +1,175 @@ +/* + * 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>(); + private final LocalBluetoothManager mBtManager; + + CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) { + mContext = context; + mBtManager = localBtManager; + } + + 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); + mBtManager.getEventManager().dispatchDeviceAdded(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..e3d2a99 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -0,0 +1,220 @@ +/* + * 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 int getConnectionState() { + return mAdapter.getConnectionState(); + } + + 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..623ccc3 --- /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, this); + 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..8f5e1f1 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -0,0 +1,377 @@ +/* + * 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.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.content.Intent; +import android.os.ParcelUuid; +import android.util.Log; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * 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..9e76933 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -0,0 +1,148 @@ +/* + * 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.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; + +/** + * 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); + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java new file mode 100644 index 0000000..e8ab220 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -0,0 +1,739 @@ +/* + * Copyright (C) 2015 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.wifi; + +import android.content.Context; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkInfo.State; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.util.Log; +import android.util.LruCache; + +import com.android.settingslib.R; + +import java.util.Map; + + +public class AccessPoint implements Comparable<AccessPoint> { + static final String TAG = "SettingsLib.AccessPoint"; + + /** + * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels + */ + public static final int LOWER_FREQ_24GHZ = 2400; + + /** + * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels + */ + public static final int HIGHER_FREQ_24GHZ = 2500; + + /** + * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels + */ + public static final int LOWER_FREQ_5GHZ = 4900; + + /** + * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels + */ + public static final int HIGHER_FREQ_5GHZ = 5900; + + + /** + * Experimental: we should be able to show the user the list of BSSIDs and bands + * for that SSID. + * For now this data is used only with Verbose Logging so as to show the band and number + * of BSSIDs on which that network is seen. + */ + public LruCache<String, ScanResult> mScanResultCache; + private static final String KEY_NETWORKINFO = "key_networkinfo"; + private static final String KEY_WIFIINFO = "key_wifiinfo"; + private static final String KEY_SCANRESULT = "key_scanresult"; + private static final String KEY_CONFIG = "key_config"; + + /** + * These values are matched in string arrays -- changes must be kept in sync + */ + public static final int SECURITY_NONE = 0; + public static final int SECURITY_WEP = 1; + public static final int SECURITY_PSK = 2; + public static final int SECURITY_EAP = 3; + + private static final int PSK_UNKNOWN = 0; + private static final int PSK_WPA = 1; + private static final int PSK_WPA2 = 2; + private static final int PSK_WPA_WPA2 = 3; + + private static final int VISIBILITY_OUTDATED_AGE_IN_MILLI = 20000; + private final Context mContext; + + private String ssid; + private int security; + private int networkId = WifiConfiguration.INVALID_NETWORK_ID; + + private int pskType = PSK_UNKNOWN; + + private WifiConfiguration mConfig; + private ScanResult mScanResult; + + private int mRssi = Integer.MAX_VALUE; + private long mSeen = 0; + + private WifiInfo mInfo; + private NetworkInfo mNetworkInfo; + private AccessPointListener mAccessPointListener; + + private Object mTag; + + public AccessPoint(Context context, Bundle savedState) { + mContext = context; + mConfig = savedState.getParcelable(KEY_CONFIG); + if (mConfig != null) { + loadConfig(mConfig); + } + mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); + if (mScanResult != null) { + loadResult(mScanResult); + } + mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); + if (savedState.containsKey(KEY_NETWORKINFO)) { + mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO); + } + update(mInfo, mNetworkInfo); + } + + AccessPoint(Context context, ScanResult result) { + mContext = context; + loadResult(result); + } + + AccessPoint(Context context, WifiConfiguration config) { + mContext = context; + loadConfig(config); + } + + @Override + public int compareTo(AccessPoint other) { + // Active one goes first. + if (isActive() && !other.isActive()) return -1; + if (!isActive() && other.isActive()) return 1; + + // Reachable one goes before unreachable one. + if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1; + if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1; + + // Configured one goes before unconfigured one. + if (networkId != WifiConfiguration.INVALID_NETWORK_ID + && other.networkId == WifiConfiguration.INVALID_NETWORK_ID) return -1; + if (networkId == WifiConfiguration.INVALID_NETWORK_ID + && other.networkId != WifiConfiguration.INVALID_NETWORK_ID) return 1; + + // Sort by signal strength. + int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi); + if (difference != 0) { + return difference; + } + // Sort by ssid. + return ssid.compareToIgnoreCase(other.ssid); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof AccessPoint)) return false; + return (this.compareTo((AccessPoint) other) == 0); + } + + @Override + public int hashCode() { + int result = 0; + if (mInfo != null) result += 13 * mInfo.hashCode(); + result += 19 * mRssi; + result += 23 * networkId; + result += 29 * ssid.hashCode(); + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder().append("AccessPoint(") + .append(ssid); + if (isSaved()) { + builder.append(',').append("saved"); + } + if (isActive()) { + builder.append(',').append("active"); + } + if (isEphemeral()) { + builder.append(',').append("ephemeral"); + } + if (isConnectable()) { + builder.append(',').append("connectable"); + } + if (security != SECURITY_NONE) { + builder.append(',').append(securityToString(security, pskType)); + } + return builder.append(')').toString(); + } + + public boolean matches(ScanResult result) { + return ssid.equals(result.SSID) && security == getSecurity(result); + } + + public boolean matches(WifiConfiguration config) { + return ssid.equals(removeDoubleQuotes(config.SSID)) && security == getSecurity(config); + } + + public WifiConfiguration getConfig() { + return mConfig; + } + + public void clearConfig() { + mConfig = null; + networkId = WifiConfiguration.INVALID_NETWORK_ID; + } + + public WifiInfo getInfo() { + return mInfo; + } + + public int getLevel() { + if (mRssi == Integer.MAX_VALUE) { + return -1; + } + return WifiManager.calculateSignalLevel(mRssi, 4); + } + + public NetworkInfo getNetworkInfo() { + return mNetworkInfo; + } + + public int getSecurity() { + return security; + } + + public String getSecurityString(boolean concise) { + Context context = mContext; + switch(security) { + case SECURITY_EAP: + return concise ? context.getString(R.string.wifi_security_short_eap) : + context.getString(R.string.wifi_security_eap); + case SECURITY_PSK: + switch (pskType) { + case PSK_WPA: + return concise ? context.getString(R.string.wifi_security_short_wpa) : + context.getString(R.string.wifi_security_wpa); + case PSK_WPA2: + return concise ? context.getString(R.string.wifi_security_short_wpa2) : + context.getString(R.string.wifi_security_wpa2); + case PSK_WPA_WPA2: + return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) : + context.getString(R.string.wifi_security_wpa_wpa2); + case PSK_UNKNOWN: + default: + return concise ? context.getString(R.string.wifi_security_short_psk_generic) + : context.getString(R.string.wifi_security_psk_generic); + } + case SECURITY_WEP: + return concise ? context.getString(R.string.wifi_security_short_wep) : + context.getString(R.string.wifi_security_wep); + case SECURITY_NONE: + default: + return concise ? "" : context.getString(R.string.wifi_security_none); + } + } + + public String getSsid() { + return ssid; + } + + public DetailedState getDetailedState() { + return mNetworkInfo != null ? mNetworkInfo.getDetailedState() : null; + } + + public String getSummary() { + // Update to new summary + StringBuilder summary = new StringBuilder(); + + if (isActive()) { // This is the active connection + summary.append(getSummary(mContext, getDetailedState(), + networkId == WifiConfiguration.INVALID_NETWORK_ID)); + } else if (mConfig != null + && mConfig.hasNoInternetAccess()) { + summary.append(mContext.getString(R.string.wifi_no_internet)); + } else if (mConfig != null && ((mConfig.status == WifiConfiguration.Status.DISABLED && + mConfig.disableReason != WifiConfiguration.DISABLED_UNKNOWN_REASON) + || mConfig.autoJoinStatus + >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE)) { + if (mConfig.autoJoinStatus + >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) { + if (mConfig.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE) { + summary.append(mContext.getString(R.string.wifi_disabled_network_failure)); + } else if (mConfig.disableReason == WifiConfiguration.DISABLED_AUTH_FAILURE) { + summary.append(mContext.getString(R.string.wifi_disabled_password_failure)); + } else { + summary.append(mContext.getString(R.string.wifi_disabled_wifi_failure)); + } + } else { + switch (mConfig.disableReason) { + case WifiConfiguration.DISABLED_AUTH_FAILURE: + summary.append(mContext.getString(R.string.wifi_disabled_password_failure)); + break; + case WifiConfiguration.DISABLED_DHCP_FAILURE: + case WifiConfiguration.DISABLED_DNS_FAILURE: + summary.append(mContext.getString(R.string.wifi_disabled_network_failure)); + break; + case WifiConfiguration.DISABLED_UNKNOWN_REASON: + case WifiConfiguration.DISABLED_ASSOCIATION_REJECT: + summary.append(mContext.getString(R.string.wifi_disabled_generic)); + break; + } + } + } else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range + summary.append(mContext.getString(R.string.wifi_not_in_range)); + } else { // In range, not disabled. + if (mConfig != null) { // Is saved network + summary.append(mContext.getString(R.string.wifi_remembered)); + } + } + + if (WifiTracker.sVerboseLogging > 0) { + // Add RSSI/band information for this config, what was seen up to 6 seconds ago + // verbose WiFi Logging is only turned on thru developers settings + if (mInfo != null && mNetworkInfo != null) { // This is the active connection + summary.append(" f=" + Integer.toString(mInfo.getFrequency())); + } + summary.append(" " + getVisibilityStatus()); + if (mConfig != null && mConfig.autoJoinStatus > 0) { + summary.append(" (" + mConfig.autoJoinStatus); + if (mConfig.blackListTimestamp > 0) { + long now = System.currentTimeMillis(); + long diff = (now - mConfig.blackListTimestamp)/1000; + long sec = diff%60; //seconds + long min = (diff/60)%60; //minutes + long hour = (min/60)%60; //hours + summary.append(", "); + if (hour > 0) summary.append(Long.toString(hour) + "h "); + summary.append( Long.toString(min) + "m "); + summary.append( Long.toString(sec) + "s "); + } + summary.append(")"); + } + if (mConfig != null && mConfig.numIpConfigFailures > 0) { + summary.append(" ipf=").append(mConfig.numIpConfigFailures); + } + if (mConfig != null && mConfig.numConnectionFailures > 0) { + summary.append(" cf=").append(mConfig.numConnectionFailures); + } + if (mConfig != null && mConfig.numAuthFailures > 0) { + summary.append(" authf=").append(mConfig.numAuthFailures); + } + if (mConfig != null && mConfig.numNoInternetAccessReports > 0) { + summary.append(" noInt=").append(mConfig.numNoInternetAccessReports); + } + } + return summary.toString(); + } + + /** + * Returns the visibility status of the WifiConfiguration. + * + * @return autojoin debugging information + * TODO: use a string formatter + * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"] + * For instance [-40,5/-30,2] + */ + private String getVisibilityStatus() { + StringBuilder visibility = new StringBuilder(); + StringBuilder scans24GHz = null; + StringBuilder scans5GHz = null; + String bssid = null; + + long now = System.currentTimeMillis(); + + if (mInfo != null) { + bssid = mInfo.getBSSID(); + if (bssid != null) { + visibility.append(" ").append(bssid); + } + visibility.append(" rssi=").append(mInfo.getRssi()); + visibility.append(" "); + visibility.append(" score=").append(mInfo.score); + visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate)); + visibility.append(String.format("%.1f,", mInfo.txRetriesRate)); + visibility.append(String.format("%.1f ", mInfo.txBadRate)); + visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate)); + } + + if (mScanResultCache != null) { + int rssi5 = WifiConfiguration.INVALID_RSSI; + int rssi24 = WifiConfiguration.INVALID_RSSI; + int num5 = 0; + int num24 = 0; + int numBlackListed = 0; + int n24 = 0; // Number scan results we included in the string + int n5 = 0; // Number scan results we included in the string + Map<String, ScanResult> list = mScanResultCache.snapshot(); + // TODO: sort list by RSSI or age + for (ScanResult result : list.values()) { + if (result.seen == 0) + continue; + + if (result.autoJoinStatus != ScanResult.ENABLED) numBlackListed++; + + if (result.frequency >= LOWER_FREQ_5GHZ + && result.frequency <= HIGHER_FREQ_5GHZ) { + // Strictly speaking: [4915, 5825] + // number of known BSSID on 5GHz band + num5 = num5 + 1; + } else if (result.frequency >= LOWER_FREQ_24GHZ + && result.frequency <= HIGHER_FREQ_24GHZ) { + // Strictly speaking: [2412, 2482] + // number of known BSSID on 2.4Ghz band + num24 = num24 + 1; + } + + // Ignore results seen, older than 20 seconds + if (now - result.seen > VISIBILITY_OUTDATED_AGE_IN_MILLI) continue; + + if (result.frequency >= LOWER_FREQ_5GHZ + && result.frequency <= HIGHER_FREQ_5GHZ) { + if (result.level > rssi5) { + rssi5 = result.level; + } + if (n5 < 4) { + if (scans5GHz == null) scans5GHz = new StringBuilder(); + scans5GHz.append(" \n{").append(result.BSSID); + if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*"); + scans5GHz.append("=").append(result.frequency); + scans5GHz.append(",").append(result.level); + if (result.autoJoinStatus != 0) { + scans5GHz.append(",st=").append(result.autoJoinStatus); + } + if (result.numIpConfigFailures != 0) { + scans5GHz.append(",ipf=").append(result.numIpConfigFailures); + } + scans5GHz.append("}"); + n5++; + } + } else if (result.frequency >= LOWER_FREQ_24GHZ + && result.frequency <= HIGHER_FREQ_24GHZ) { + if (result.level > rssi24) { + rssi24 = result.level; + } + if (n24 < 4) { + if (scans24GHz == null) scans24GHz = new StringBuilder(); + scans24GHz.append(" \n{").append(result.BSSID); + if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*"); + scans24GHz.append("=").append(result.frequency); + scans24GHz.append(",").append(result.level); + if (result.autoJoinStatus != 0) { + scans24GHz.append(",st=").append(result.autoJoinStatus); + } + if (result.numIpConfigFailures != 0) { + scans24GHz.append(",ipf=").append(result.numIpConfigFailures); + } + scans24GHz.append("}"); + n24++; + } + } + } + visibility.append(" ["); + if (num24 > 0) { + visibility.append("(").append(num24).append(")"); + if (n24 <= 4) { + if (scans24GHz != null) { + visibility.append(scans24GHz.toString()); + } + } else { + visibility.append("max=").append(rssi24); + if (scans24GHz != null) { + visibility.append(",").append(scans24GHz.toString()); + } + } + } + visibility.append(";"); + if (num5 > 0) { + visibility.append("(").append(num5).append(")"); + if (n5 <= 4) { + if (scans5GHz != null) { + visibility.append(scans5GHz.toString()); + } + } else { + visibility.append("max=").append(rssi5); + if (scans5GHz != null) { + visibility.append(",").append(scans5GHz.toString()); + } + } + } + if (numBlackListed > 0) + visibility.append("!").append(numBlackListed); + visibility.append("]"); + } else { + if (mRssi != Integer.MAX_VALUE) { + visibility.append(" rssi="); + visibility.append(mRssi); + if (mScanResult != null) { + visibility.append(", f="); + visibility.append(mScanResult.frequency); + } + } + } + + return visibility.toString(); + } + + /** + * Return whether this is the active connection. + * For ephemeral connections (networkId is invalid), this returns false if the network is + * disconnected. + */ + public boolean isActive() { + return mNetworkInfo != null && + (networkId != WifiConfiguration.INVALID_NETWORK_ID || + mNetworkInfo.getState() != State.DISCONNECTED); + } + + public boolean isConnectable() { + return getLevel() != -1 && getDetailedState() == null; + } + + public boolean isEphemeral() { + return !isSaved() && mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED; + } + + /** Return whether the given {@link WifiInfo} is for this access point. */ + private boolean isInfoForThisAccessPoint(WifiInfo info) { + if (networkId != WifiConfiguration.INVALID_NETWORK_ID) { + return networkId == info.getNetworkId(); + } else { + // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID. + // (Note that we only do this if the WifiConfiguration explicitly equals INVALID). + // TODO: Handle hex string SSIDs. + return ssid.equals(removeDoubleQuotes(info.getSSID())); + } + } + + public boolean isSaved() { + return networkId != WifiConfiguration.INVALID_NETWORK_ID; + } + + public Object getTag() { + return mTag; + } + + public void setTag(Object tag) { + mTag = tag; + } + + /** + * Generate and save a default wifiConfiguration with common values. + * Can only be called for unsecured networks. + */ + public void generateOpenNetworkConfig() { + if (security != SECURITY_NONE) + throw new IllegalStateException(); + if (mConfig != null) + return; + mConfig = new WifiConfiguration(); + mConfig.SSID = AccessPoint.convertToQuotedString(ssid); + mConfig.allowedKeyManagement.set(KeyMgmt.NONE); + } + + void loadConfig(WifiConfiguration config) { + ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); + security = getSecurity(config); + networkId = config.networkId; + mConfig = config; + } + + private void loadResult(ScanResult result) { + ssid = result.SSID; + security = getSecurity(result); + if (security == SECURITY_PSK) + pskType = getPskType(result); + mRssi = result.level; + mScanResult = result; + if (result.seen > mSeen) { + mSeen = result.seen; + } + } + + public void saveWifiState(Bundle savedState) { + savedState.putParcelable(KEY_CONFIG, mConfig); + savedState.putParcelable(KEY_SCANRESULT, mScanResult); + savedState.putParcelable(KEY_WIFIINFO, mInfo); + if (mNetworkInfo != null) { + savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo); + } + } + + public void setListener(AccessPointListener listener) { + mAccessPointListener = listener; + } + + boolean update(ScanResult result) { + if (result.seen > mSeen) { + mSeen = result.seen; + } + if (WifiTracker.sVerboseLogging > 0) { + if (mScanResultCache == null) { + mScanResultCache = new LruCache<String, ScanResult>(32); + } + mScanResultCache.put(result.BSSID, result); + } + + if (ssid.equals(result.SSID) && security == getSecurity(result)) { + if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) { + int oldLevel = getLevel(); + mRssi = result.level; + if (getLevel() != oldLevel && mAccessPointListener != null) { + mAccessPointListener.onLevelChanged(this); + } + } + // This flag only comes from scans, is not easily saved in config + if (security == SECURITY_PSK) { + pskType = getPskType(result); + } + mScanResult = result; + if (mAccessPointListener != null) { + mAccessPointListener.onAccessPointChanged(this); + } + return true; + } + return false; + } + + boolean update(WifiInfo info, NetworkInfo networkInfo) { + boolean reorder = false; + if (info != null && isInfoForThisAccessPoint(info)) { + reorder = (mInfo == null); + mRssi = info.getRssi(); + mInfo = info; + mNetworkInfo = networkInfo; + if (mAccessPointListener != null) { + mAccessPointListener.onAccessPointChanged(this); + } + } else if (mInfo != null) { + reorder = true; + mInfo = null; + mNetworkInfo = null; + if (mAccessPointListener != null) { + mAccessPointListener.onAccessPointChanged(this); + } + } + return reorder; + } + + public static String getSummary(Context context, String ssid, DetailedState state, + boolean isEphemeral) { + if (state == DetailedState.CONNECTED && isEphemeral && ssid == null) { + // Special case for connected + ephemeral networks. + return context.getString(R.string.connected_via_wfa); + } + + String[] formats = context.getResources().getStringArray((ssid == null) + ? R.array.wifi_status : R.array.wifi_status_with_ssid); + int index = state.ordinal(); + + if (index >= formats.length || formats[index].length() == 0) { + return null; + } + return String.format(formats[index], ssid); + } + + public static String getSummary(Context context, DetailedState state, boolean isEphemeral) { + return getSummary(context, null, state, isEphemeral); + } + + public static String convertToQuotedString(String string) { + return "\"" + string + "\""; + } + + private static int getPskType(ScanResult result) { + boolean wpa = result.capabilities.contains("WPA-PSK"); + boolean wpa2 = result.capabilities.contains("WPA2-PSK"); + if (wpa2 && wpa) { + return PSK_WPA_WPA2; + } else if (wpa2) { + return PSK_WPA2; + } else if (wpa) { + return PSK_WPA; + } else { + Log.w(TAG, "Received abnormal flag string: " + result.capabilities); + return PSK_UNKNOWN; + } + } + + private static int getSecurity(ScanResult result) { + if (result.capabilities.contains("WEP")) { + return SECURITY_WEP; + } else if (result.capabilities.contains("PSK")) { + return SECURITY_PSK; + } else if (result.capabilities.contains("EAP")) { + return SECURITY_EAP; + } + return SECURITY_NONE; + } + + static int getSecurity(WifiConfiguration config) { + if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { + return SECURITY_PSK; + } + if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) || + config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { + return SECURITY_EAP; + } + return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE; + } + + public static String securityToString(int security, int pskType) { + if (security == SECURITY_WEP) { + return "WEP"; + } else if (security == SECURITY_PSK) { + if (pskType == PSK_WPA) { + return "WPA"; + } else if (pskType == PSK_WPA2) { + return "WPA2"; + } else if (pskType == PSK_WPA_WPA2) { + return "WPA_WPA2"; + } + return "PSK"; + } else if (security == SECURITY_EAP) { + return "EAP"; + } + return "NONE"; + } + + static String removeDoubleQuotes(String string) { + int length = string.length(); + if ((length > 1) && (string.charAt(0) == '"') + && (string.charAt(length - 1) == '"')) { + return string.substring(1, length - 1); + } + return string; + } + + public interface AccessPointListener { + void onAccessPointChanged(AccessPoint accessPoint); + void onLevelChanged(AccessPoint accessPoint); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java new file mode 100644 index 0000000..2eb7abf --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2015 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.wifi; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.Message; +import android.widget.Toast; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.R; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Tracks saved or available wifi networks and their state. + */ +public class WifiTracker { + private static final String TAG = "WifiTracker"; + + /** verbose logging flag. this flag is set thru developer debugging options + * and used so as to assist with in-the-field WiFi connectivity debugging */ + public static int sVerboseLogging = 0; + + // TODO: Allow control of this? + // Combo scans can take 5-6s to complete - set to 10s. + private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; + + private final Context mContext; + private final WifiManager mWifiManager; + private final IntentFilter mFilter; + + private final AtomicBoolean mConnected = new AtomicBoolean(false); + private final WifiListener mListener; + private final boolean mIncludeSaved; + private final boolean mIncludeScans; + + private boolean mSavedNetworksExist; + private boolean mRegistered; + private ArrayList<AccessPoint> mAccessPoints = new ArrayList<>(); + private ArrayList<AccessPoint> mCachedAccessPoints = new ArrayList<>(); + + private NetworkInfo mLastNetworkInfo; + private WifiInfo mLastInfo; + + @VisibleForTesting + Scanner mScanner; + + public WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, + boolean includeScans) { + this(context, wifiListener, includeSaved, includeScans, + (WifiManager) context.getSystemService(Context.WIFI_SERVICE)); + } + + @VisibleForTesting + WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, + boolean includeScans, WifiManager wifiManager) { + if (!includeSaved && !includeScans) { + throw new IllegalArgumentException("Must include either saved or scans"); + } + mContext = context; + mWifiManager = wifiManager; + mIncludeSaved = includeSaved; + mIncludeScans = includeScans; + mListener = wifiListener; + + // check if verbose logging has been turned on or off + sVerboseLogging = mWifiManager.getVerboseLoggingLevel(); + + mFilter = new IntentFilter(); + mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); + mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); + mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); + mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + } + + /** + * Forces an update of the wifi networks when not scanning. + */ + public void forceUpdate() { + updateAccessPoints(); + } + + /** + * Force a scan for wifi networks to happen now. + */ + public void forceScan() { + if (mWifiManager.isWifiEnabled() && mScanner != null) { + mScanner.forceScan(); + } + } + + /** + * Temporarily stop scanning for wifi networks. + */ + public void pauseScanning() { + if (mScanner != null) { + mScanner.pause(); + mScanner = null; + } + } + + /** + * Resume scanning for wifi networks after it has been paused. + */ + public void resumeScanning() { + if (mScanner == null) { + mScanner = new Scanner(); + } + if (mWifiManager.isWifiEnabled()) { + mScanner.resume(); + } + updateAccessPoints(); + } + + /** + * Start tracking wifi networks. + * Registers listeners and starts scanning for wifi networks. If this is not called + * then forceUpdate() must be called to populate getAccessPoints(). + */ + public void startTracking() { + resumeScanning(); + if (!mRegistered) { + mContext.registerReceiver(mReceiver, mFilter); + mRegistered = true; + } + } + + /** + * Stop tracking wifi networks. + * Unregisters all listeners and stops scanning for wifi networks. This should always + * be called when done with a WifiTracker (if startTracking was called) to ensure + * proper cleanup. + */ + public void stopTracking() { + if (mRegistered) { + mContext.unregisterReceiver(mReceiver); + mRegistered = false; + } + pauseScanning(); + } + + /** + * Gets the current list of access points. + */ + public List<AccessPoint> getAccessPoints() { + return mAccessPoints; + } + + public WifiManager getManager() { + return mWifiManager; + } + + public boolean isWifiEnabled() { + return mWifiManager.isWifiEnabled(); + } + + /** + * @return true when there are saved networks on the device, regardless + * of whether the WifiTracker is tracking saved networks. + */ + public boolean doSavedNetworksExist() { + return mSavedNetworksExist; + } + + public boolean isConnected() { + return mConnected.get(); + } + + public void dump(PrintWriter pw) { + pw.println(" - wifi tracker ------"); + for (AccessPoint accessPoint : mAccessPoints) { + pw.println(" " + accessPoint); + } + } + + private void updateAccessPoints() { + // Swap the current access points into a cached list. + ArrayList<AccessPoint> tmpSwp = mAccessPoints; + mAccessPoints = mCachedAccessPoints; + mCachedAccessPoints = tmpSwp; + // Clear out the configs so we don't think something is saved when it isn't. + for (AccessPoint accessPoint : mCachedAccessPoints) { + accessPoint.clearConfig(); + } + + mAccessPoints.clear(); + + /** Lookup table to more quickly update AccessPoints by only considering objects with the + * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ + Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); + + final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); + if (configs != null) { + mSavedNetworksExist = configs.size() != 0; + for (WifiConfiguration config : configs) { + if (config.selfAdded && config.numAssociation == 0) { + continue; + } + AccessPoint accessPoint = getCachedOrCreate(config); + if (mLastInfo != null && mLastNetworkInfo != null) { + accessPoint.update(mLastInfo, mLastNetworkInfo); + } + if (mIncludeSaved) { + mAccessPoints.add(accessPoint); + apMap.put(accessPoint.getSsid(), accessPoint); + } else { + // If we aren't using saved networks, drop them into the cache so that + // we have access to their saved info. + mCachedAccessPoints.add(accessPoint); + } + } + } + + final List<ScanResult> results = mWifiManager.getScanResults(); + if (results != null) { + for (ScanResult result : results) { + // Ignore hidden and ad-hoc networks. + if (result.SSID == null || result.SSID.length() == 0 || + result.capabilities.contains("[IBSS]")) { + continue; + } + + boolean found = false; + for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { + if (accessPoint.update(result)) { + found = true; + break; + } + } + if (!found && mIncludeScans) { + AccessPoint accessPoint = getCachedOrCreate(result); + if (mLastInfo != null && mLastNetworkInfo != null) { + accessPoint.update(mLastInfo, mLastNetworkInfo); + } + mAccessPoints.add(accessPoint); + apMap.put(accessPoint.getSsid(), accessPoint); + } + } + } + + // Pre-sort accessPoints to speed preference insertion + Collections.sort(mAccessPoints); + if (mListener != null) { + mListener.onAccessPointsChanged(); + } + } + + private AccessPoint getCachedOrCreate(ScanResult result) { + final int N = mCachedAccessPoints.size(); + for (int i = 0; i < N; i++) { + if (mCachedAccessPoints.get(i).matches(result)) { + AccessPoint ret = mCachedAccessPoints.remove(i); + ret.update(result); + return ret; + } + } + return new AccessPoint(mContext, result); + } + + private AccessPoint getCachedOrCreate(WifiConfiguration config) { + final int N = mCachedAccessPoints.size(); + for (int i = 0; i < N; i++) { + if (mCachedAccessPoints.get(i).matches(config)) { + AccessPoint ret = mCachedAccessPoints.remove(i); + ret.loadConfig(config); + return ret; + } + } + return new AccessPoint(mContext, config); + } + + private void updateNetworkInfo(NetworkInfo networkInfo) { + /* sticky broadcasts can call this when wifi is disabled */ + if (!mWifiManager.isWifiEnabled()) { + mScanner.pause(); + return; + } + + if (networkInfo != null && + networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) { + mScanner.pause(); + } else { + mScanner.resume(); + } + + mLastInfo = mWifiManager.getConnectionInfo(); + if (networkInfo != null) { + mLastNetworkInfo = networkInfo; + } + + boolean reorder = false; + for (int i = mAccessPoints.size() - 1; i >= 0; --i) { + if (mAccessPoints.get(i).update(mLastInfo, mLastNetworkInfo)) { + reorder = true; + } + } + if (reorder) { + Collections.sort(mAccessPoints); + if (mListener != null) { + mListener.onAccessPointsChanged(); + } + } + } + + private void updateWifiState(int state) { + if (state == WifiManager.WIFI_STATE_ENABLED) { + if (mScanner != null) { + // We only need to resume if mScanner isn't null because + // that means we want to be scanning. + mScanner.resume(); + } + } else { + mLastInfo = null; + mLastNetworkInfo = null; + if (mScanner != null) { + mScanner.pause(); + } + } + if (mListener != null) { + mListener.onWifiStateChanged(state); + } + } + + public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved, + boolean includeScans) { + WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans); + tracker.forceUpdate(); + return tracker.getAccessPoints(); + } + + @VisibleForTesting + final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { + updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN)); + } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || + WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || + WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { + updateAccessPoints(); + } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { + NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_INFO); + mConnected.set(info.isConnected()); + if (mListener != null) { + mListener.onConnectedChanged(); + } + updateAccessPoints(); + updateNetworkInfo(info); + } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { + updateNetworkInfo(null); + } + } + }; + + @VisibleForTesting + class Scanner extends Handler { + private static final int MSG_SCAN = 0; + + private int mRetry = 0; + + void resume() { + if (!hasMessages(MSG_SCAN)) { + sendEmptyMessage(MSG_SCAN); + } + } + + void forceScan() { + removeMessages(MSG_SCAN); + sendEmptyMessage(MSG_SCAN); + } + + void pause() { + mRetry = 0; + removeMessages(MSG_SCAN); + } + + @VisibleForTesting + boolean isScanning() { + return hasMessages(MSG_SCAN); + } + + @Override + public void handleMessage(Message message) { + if (message.what != MSG_SCAN) return; + if (mWifiManager.startScan()) { + mRetry = 0; + } else if (++mRetry >= 3) { + mRetry = 0; + if (mContext != null) { + Toast.makeText(mContext, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); + } + return; + } + sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS); + } + } + + /** A restricted multimap for use in constructAccessPoints */ + private static class Multimap<K,V> { + private final HashMap<K,List<V>> store = new HashMap<K,List<V>>(); + /** retrieve a non-null list of values with key K */ + List<V> getAll(K key) { + List<V> values = store.get(key); + return values != null ? values : Collections.<V>emptyList(); + } + + void put(K key, V val) { + List<V> curVals = store.get(key); + if (curVals == null) { + curVals = new ArrayList<V>(3); + store.put(key, curVals); + } + curVals.add(val); + } + } + + public interface WifiListener { + /** + * Called when the state of Wifi has changed, the state will be one of + * the following. + * + * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li> + * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li> + * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li> + * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li> + * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li> + * <p> + * + * @param state The new state of wifi. + */ + void onWifiStateChanged(int state); + + /** + * Called when the connection state of wifi has changed and isConnected + * should be called to get the updated state. + */ + void onConnectedChanged(); + + /** + * Called to indicate the list of AccessPoints has been updated and + * getAccessPoints should be called to get the latest information. + */ + void onAccessPointsChanged(); + } +} diff --git a/packages/SettingsLib/tests/Android.mk b/packages/SettingsLib/tests/Android.mk new file mode 100644 index 0000000..d3ffffa --- /dev/null +++ b/packages/SettingsLib/tests/Android.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2015 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common + +LOCAL_PACKAGE_NAME := SettingsLibTests + +LOCAL_STATIC_JAVA_LIBRARIES := mockito-target + +include frameworks/base/packages/SettingsLib/common.mk + +include $(BUILD_PACKAGE) diff --git a/packages/SettingsLib/tests/AndroidManifest.xml b/packages/SettingsLib/tests/AndroidManifest.xml new file mode 100644 index 0000000..00d16fc --- /dev/null +++ b/packages/SettingsLib/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.settingslib" + android:label="Tests for SettingsLib"> + </instrumentation> +</manifest> diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/BaseTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/BaseTest.java new file mode 100644 index 0000000..04a568e --- /dev/null +++ b/packages/SettingsLib/tests/src/com/android/settingslib/BaseTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 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; + +import android.test.AndroidTestCase; + +public class BaseTest extends AndroidTestCase { + + @Override + protected void setUp() throws Exception { + super.setUp(); + // Mockito stuff. + System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath()); + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } + +} diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/AccessPointTest.java new file mode 100644 index 0000000..4ac461d --- /dev/null +++ b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/AccessPointTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 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.wifi; + +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; + +import com.android.settingslib.BaseTest; +import com.android.settingslib.wifi.AccessPoint.AccessPointListener; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +// TODO: Add some coverage +public class AccessPointTest extends BaseTest { + + private static final String TEST_SSID = "TestSsid"; + private static final int NETWORK_ID = 0; + + private AccessPointListener mAccessPointListener; + private AccessPoint mAccessPoint; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mAccessPointListener = Mockito.mock(AccessPointListener.class); + + WifiConfiguration wifiConfig = new WifiConfiguration(); + wifiConfig.networkId = NETWORK_ID; + wifiConfig.SSID = TEST_SSID; + + mAccessPoint = new AccessPoint(mContext, wifiConfig); + mAccessPoint.setListener(mAccessPointListener); + } + + public void testOnLevelChanged() { + ScanResult result = new ScanResult(); + result.capabilities = ""; + result.SSID = TEST_SSID; + + // Give it a level. + result.level = WifiTrackerTest.levelToRssi(1); + mAccessPoint.update(result); + verifyOnLevelChangedCallback(1); + + // Give it a better level. + result.level = WifiTrackerTest.levelToRssi(2); + mAccessPoint.update(result); + verifyOnLevelChangedCallback(1); + } + + public void testOnAccessPointChangedCallback() { + WifiInfo wifiInfo = Mockito.mock(WifiInfo.class); + Mockito.when(wifiInfo.getNetworkId()).thenReturn(NETWORK_ID); + + mAccessPoint.update(wifiInfo, null); + verifyOnAccessPointsCallback(1); + + mAccessPoint.update(null, null); + verifyOnAccessPointsCallback(2); + + ScanResult result = new ScanResult(); + result.capabilities = ""; + result.SSID = TEST_SSID; + mAccessPoint.update(result); + verifyOnAccessPointsCallback(3); + } + + private void verifyOnLevelChangedCallback(int num) { + ArgumentCaptor<AccessPoint> accessPoint = ArgumentCaptor.forClass(AccessPoint.class); + Mockito.verify(mAccessPointListener, Mockito.atLeast(num)) + .onLevelChanged(accessPoint.capture()); + assertEquals(mAccessPoint, accessPoint.getValue()); + } + + private void verifyOnAccessPointsCallback(int num) { + ArgumentCaptor<AccessPoint> accessPoint = ArgumentCaptor.forClass(AccessPoint.class); + Mockito.verify(mAccessPointListener, Mockito.atLeast(num)) + .onAccessPointChanged(accessPoint.capture()); + assertEquals(mAccessPoint, accessPoint.getValue()); + } + +} diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java new file mode 100644 index 0000000..8eb1ca4 --- /dev/null +++ b/packages/SettingsLib/tests/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2015 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.wifi; + +import android.content.Intent; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiSsid; +import android.util.Log; + +import com.android.settingslib.BaseTest; +import com.android.settingslib.wifi.WifiTracker.WifiListener; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +public class WifiTrackerTest extends BaseTest { + + private static final String TAG = "WifiTrackerTest"; + + private static final String[] TEST_SSIDS = new String[] { + "TEST_SSID_1", + "TEST_SSID_2", + "TEST_SSID_3", + "TEST_SSID_4", + "TEST_SSID_5", + }; + private static final int NUM_NETWORKS = 5; + + private WifiManager mWifiManager; + private WifiListener mWifiListener; + + private WifiTracker mWifiTracker; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mWifiManager = Mockito.mock(WifiManager.class); + mWifiListener = Mockito.mock(WifiListener.class); + mWifiTracker = new WifiTracker(mContext, mWifiListener, true, true, mWifiManager); + mWifiTracker.mScanner = mWifiTracker.new Scanner(); + Mockito.when(mWifiManager.isWifiEnabled()).thenReturn(true); + } + + @Override + protected void tearDown() throws Exception { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + mWifiTracker.dump(pw); + pw.flush(); + Log.d(TAG, sw.toString()); + super.tearDown(); + } + + public void testAccessPointsCallback() { + sendScanResultsAvailable(); + + Mockito.verify(mWifiListener, Mockito.atLeastOnce()).onAccessPointsChanged(); + } + + public void testConnectedCallback() { + sendConnected(); + + Mockito.verify(mWifiListener, Mockito.atLeastOnce()).onConnectedChanged(); + assertEquals(true, mWifiTracker.isConnected()); + } + + public void testWifiStateCallback() { + final int TEST_WIFI_STATE = WifiManager.WIFI_STATE_ENABLED; + + Intent i = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); + i.putExtra(WifiManager.EXTRA_WIFI_STATE, TEST_WIFI_STATE); + mWifiTracker.mReceiver.onReceive(mContext, i); + + ArgumentCaptor<Integer> wifiState = ArgumentCaptor.forClass(Integer.class); + Mockito.verify(mWifiListener, Mockito.atLeastOnce()) + .onWifiStateChanged(wifiState.capture()); + assertEquals(TEST_WIFI_STATE, (int) wifiState.getValue()); + } + + public void testScanner() { + // TODO: Figure out how to verify more of the Scanner functionality. + // Make scans be successful. + Mockito.when(mWifiManager.startScan()).thenReturn(true); + + mWifiTracker.mScanner.handleMessage(null); + Mockito.verify(mWifiManager, Mockito.atLeastOnce()).startScan(); + } + + public void testNetworkSorting() { + List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>(); + List<ScanResult> scanResults = new ArrayList<ScanResult>(); + String[] expectedSsids = generateTestNetworks(wifiConfigs, scanResults, true); + + // Tell WifiTracker we are connected now. + sendConnected(); + + // Send all of the configs and scan results to the tracker. + Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs); + Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults); + sendScanResultsAvailable(); + + List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints(); + assertEquals("Expected number of results", NUM_NETWORKS, accessPoints.size()); + for (int i = 0; i < NUM_NETWORKS; i++) { + assertEquals("Verifying slot " + i, expectedSsids[i], accessPoints.get(i).getSsid()); + } + } + + public void testSavedOnly() { + mWifiTracker = new WifiTracker(mContext, mWifiListener, true, false, mWifiManager); + mWifiTracker.mScanner = mWifiTracker.new Scanner(); + + List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>(); + List<ScanResult> scanResults = new ArrayList<ScanResult>(); + generateTestNetworks(wifiConfigs, scanResults, true); + + // Tell WifiTracker we are connected now. + sendConnected(); + + // Send all of the configs and scan results to the tracker. + Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs); + Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults); + sendScanResultsAvailable(); + + List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints(); + // Only expect the first two to come back in the results. + assertEquals("Expected number of results", 2, accessPoints.size()); + assertEquals(TEST_SSIDS[1], accessPoints.get(0).getSsid()); + assertEquals(TEST_SSIDS[0], accessPoints.get(1).getSsid()); + } + + public void testAvailableOnly() { + mWifiTracker = new WifiTracker(mContext, mWifiListener, false, true, mWifiManager); + mWifiTracker.mScanner = mWifiTracker.new Scanner(); + + List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>(); + List<ScanResult> scanResults = new ArrayList<ScanResult>(); + String[] expectedSsids = generateTestNetworks(wifiConfigs, scanResults, true); + + // Tell WifiTracker we are connected now. + sendConnected(); + + // Send all of the configs and scan results to the tracker. + Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs); + Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults); + sendScanResultsAvailable(); + + // Expect the last one (sorted order) to be left off since its only saved. + List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints(); + assertEquals("Expected number of results", NUM_NETWORKS - 1, accessPoints.size()); + for (int i = 0; i < NUM_NETWORKS - 1; i++) { + assertEquals("Verifying slot " + i, expectedSsids[i], accessPoints.get(i).getSsid()); + } + } + + public void testNonEphemeralConnected() { + mWifiTracker = new WifiTracker(mContext, mWifiListener, false, true, mWifiManager); + mWifiTracker.mScanner = mWifiTracker.new Scanner(); + + List<WifiConfiguration> wifiConfigs = new ArrayList<WifiConfiguration>(); + List<ScanResult> scanResults = new ArrayList<ScanResult>(); + String[] expectedSsids = generateTestNetworks(wifiConfigs, scanResults, false); + + // Tell WifiTracker we are connected now. + sendConnected(); + + // Send all of the configs and scan results to the tracker. + Mockito.when(mWifiManager.getConfiguredNetworks()).thenReturn(wifiConfigs); + Mockito.when(mWifiManager.getScanResults()).thenReturn(scanResults); + sendScanResultsAvailable(); + // Do this twice to catch a bug that was happening in the caching, making things ephemeral. + sendScanResultsAvailable(); + + List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints(); + assertEquals("Expected number of results", NUM_NETWORKS - 1, accessPoints.size()); + assertFalse("Connection is not ephemeral", accessPoints.get(0).isEphemeral()); + assertTrue("Connected to wifi", accessPoints.get(0).isActive()); + } + + public void testEnableResumeScanning() { + mWifiTracker.mScanner = null; + + Intent i = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); + // Make sure disable/enable cycle works with no scanner (no crashing). + i.putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED); + mWifiTracker.mReceiver.onReceive(mContext, i); + i.putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED); + mWifiTracker.mReceiver.onReceive(mContext, i); + + Mockito.when(mWifiManager.isWifiEnabled()).thenReturn(false); + i.putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED); + mWifiTracker.mReceiver.onReceive(mContext, i); + // Now enable scanning while wifi is off, it shouldn't start. + mWifiTracker.resumeScanning(); + assertFalse(mWifiTracker.mScanner.isScanning()); + + // Turn on wifi and make sure scanning starts. + i.putExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_ENABLED); + mWifiTracker.mReceiver.onReceive(mContext, i); + assertTrue(mWifiTracker.mScanner.isScanning()); + } + + private String[] generateTestNetworks(List<WifiConfiguration> wifiConfigs, + List<ScanResult> scanResults, boolean connectedIsEphemeral) { + String[] expectedSsids = new String[NUM_NETWORKS]; + + // First is just saved; + addConfig(wifiConfigs, TEST_SSIDS[0]); + // This should come last since its not available. + expectedSsids[4] = TEST_SSIDS[0]; + + // Second is saved and available. + addConfig(wifiConfigs, TEST_SSIDS[1]); + addResult(scanResults, TEST_SSIDS[1], 0); + // This one is going to have a couple extra results, to verify de-duplication. + addResult(scanResults, TEST_SSIDS[1], 2); + addResult(scanResults, TEST_SSIDS[1], 1); + // This should come second since it is available and saved but not connected. + expectedSsids[1] = TEST_SSIDS[1]; + + // Third is just available, but higher rssi. + addResult(scanResults, TEST_SSIDS[2], 3); + // This comes after the next one since it has a lower rssi. + expectedSsids[3] = TEST_SSIDS[2]; + + // Fourth also just available but with even higher rssi. + addResult(scanResults, TEST_SSIDS[3], 4); + // This is the highest rssi but not saved so it should be after the saved+availables. + expectedSsids[2] = TEST_SSIDS[3]; + + // Last is going to be connected. + int netId = WifiConfiguration.INVALID_NETWORK_ID; + if (!connectedIsEphemeral) { + netId = addConfig(wifiConfigs, TEST_SSIDS[4]); + } + addResult(scanResults, TEST_SSIDS[4], 2); + // Setup wifi connection to be this one. + WifiInfo wifiInfo = Mockito.mock(WifiInfo.class); + Mockito.when(wifiInfo.getSSID()).thenReturn(TEST_SSIDS[4]); + Mockito.when(wifiInfo.getNetworkId()).thenReturn(netId); + Mockito.when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo); + // This should come first since it is connected. + expectedSsids[0] = TEST_SSIDS[4]; + + return expectedSsids; + } + + private void addResult(List<ScanResult> results, String ssid, int level) { + results.add(new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), + ssid, ssid, levelToRssi(level), AccessPoint.LOWER_FREQ_24GHZ, 0)); + } + + public static int levelToRssi(int level) { + // Reverse level to rssi calculation based off from WifiManager.calculateSignalLevel. + final int MAX_RSSI = -55; + final int MIN_RSSI = -100; + final int NUM_LEVELS = 4; + return level * (MAX_RSSI - MIN_RSSI) / (NUM_LEVELS - 1) + MIN_RSSI; + } + + private int addConfig(List<WifiConfiguration> configs, String ssid) { + WifiConfiguration config = new WifiConfiguration(); + config.networkId = configs.size(); + config.SSID = '"' + ssid + '"'; + configs.add(config); + return config.networkId; + } + + private void sendConnected() { + NetworkInfo networkInfo = Mockito.mock(NetworkInfo.class); + Mockito.when(networkInfo.isConnected()).thenReturn(true); + Mockito.when(networkInfo.getState()).thenReturn(State.CONNECTED); + Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); + mWifiTracker.mReceiver.onReceive(mContext, intent); + } + + private void sendScanResultsAvailable() { + Intent i = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mWifiTracker.mReceiver.onReceive(mContext, i); + } + +} |