Merge "PBAP server, send favorite contacts"
diff --git a/.clang-format b/.clang-format
index 8b28743..85098f2 100644
--- a/.clang-format
+++ b/.clang-format
@@ -27,3 +27,4 @@
Language: Java
# Java format is handled by check_style hook
DisableFormat: true
+SortIncludes: false
diff --git a/Android.bp b/Android.bp
index 726a790..08e0ee1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,3 +1,93 @@
-subdirs = [
- "jni",
-]
+// MAP API module
+
+java_library {
+ name: "bluetooth.mapsapi",
+
+ srcs: ["lib/mapapi/**/*.java"],
+}
+
+// Bluetooth JNI
+
+cc_library_shared {
+ name: "libbluetooth_jni",
+ srcs: ["jni/**/*.cpp"],
+ header_libs: ["libbluetooth_headers"],
+ include_dirs: [
+ "system/bt/types",
+ ],
+ shared_libs: [
+ "libbase",
+ "libchrome",
+ "liblog",
+ ],
+ static_libs: [
+ "libbluetooth-types",
+ // TODO(b/148645937) move this back to shared_libs
+ "libnativehelper",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ "-Wno-unused-parameter",
+ ],
+ sanitize: {
+ scs: true,
+ },
+}
+
+// Bluetooth APK
+
+android_app {
+ name: "Bluetooth",
+
+ srcs: [
+ "src/**/*.java",
+ ":statslog-bluetooth-java-gen",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+
+ jni_libs: ["libbluetooth_jni"],
+ libs: [
+ "javax.obex",
+ "services.net",
+ ],
+ static_libs: [
+ "com.android.vcard",
+ "bluetooth.mapsapi",
+ "sap-api-java-static",
+ "services.net",
+ "libprotobuf-java-lite",
+ "bluetooth-protos-lite",
+ "androidx.core_core",
+ "androidx.legacy_legacy-support-v4",
+ "androidx.lifecycle_lifecycle-livedata",
+ "androidx.room_room-runtime",
+ "guava",
+ ],
+
+ plugins: [
+ "androidx.room_room-compiler-plugin",
+ ],
+
+ // Add in path to Bluetooth directory because local path does not exist
+ javacflags: ["-Aroom.schemaLocation=packages/apps/Bluetooth/tests/unit/src/com/android/bluetooth/btservice/storage/schemas"],
+
+ optimize: {
+ enabled: false,
+ },
+ required: ["libbluetooth"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.bluetooth.updatable",
+ ],
+}
+
+genrule {
+ name: "statslog-bluetooth-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module bluetooth"
+ + " --javaPackage com.android.bluetooth --javaClass BluetoothStatsLog --worksource",
+ out: ["com/android/bluetooth/BluetoothStatsLog.java"],
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 0f80f52..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,85 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-# MAP API module
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, lib/mapapi)
-LOCAL_MODULE := bluetooth.mapsapi
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Bluetooth APK
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := Bluetooth
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_CERTIFICATE := platform
-LOCAL_USE_AAPT2 := true
-LOCAL_JNI_SHARED_LIBRARIES := libbluetooth_jni
-LOCAL_JAVA_LIBRARIES := javax.obex telephony-common services.net
-LOCAL_STATIC_JAVA_LIBRARIES := \
- com.android.vcard \
- bluetooth.mapsapi \
- sap-api-java-static \
- services.net \
- libprotobuf-java-lite \
- bluetooth-protos-lite \
- guava \
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- androidx.core_core \
- androidx.legacy_legacy-support-v4 \
- androidx.lifecycle_lifecycle-livedata \
- androidx.room_room-runtime \
- bt-androidx-room-runtime-nodeps \
-
-LOCAL_ANNOTATION_PROCESSORS := \
- bt-androidx-annotation-nodeps \
- bt-androidx-room-common-nodeps \
- bt-androidx-room-compiler-nodeps \
- bt-androidx-room-migration-nodeps \
- bt-antlr4-nodeps \
- bt-apache-commons-codec-nodeps \
- bt-auto-common-nodeps \
- bt-javapoet-nodeps \
- bt-kotlin-metadata-nodeps \
- bt-sqlite-jdbc-nodeps \
- bt-jetbrain-nodeps \
- guava-21.0 \
- kotlin-stdlib \
- gson-prebuilt-jar
-
-LOCAL_JAVACFLAGS := \
- -Aroom.schemaLocation=$(LOCAL_PATH)/tests/unit/src/com/android/bluetooth/btservice/storage/schemas
-
-LOCAL_ANNOTATION_PROCESSOR_CLASSES := \
- androidx.room.RoomProcessor
-
-LOCAL_REQUIRED_MODULES := libbluetooth
-LOCAL_PROGUARD_ENABLED := disabled
-include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
-include $(CLEAR_VARS)
-
-COMMON_LIBS_PATH := ../../../../../prebuilts/tools/common/m2/repository
-ROOM_LIBS_PATH := ../../lib/room
-
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
- bt-androidx-annotation-nodeps:$(ROOM_LIBS_PATH)/annotation-1.0.0-beta01.jar \
- bt-androidx-room-common-nodeps:$(ROOM_LIBS_PATH)/room-common-2.0.0-beta01.jar \
- bt-androidx-room-compiler-nodeps:$(ROOM_LIBS_PATH)/room-compiler-2.0.0-beta01.jar \
- bt-androidx-room-migration-nodeps:$(ROOM_LIBS_PATH)/room-migration-2.0.0-beta01.jar \
- bt-androidx-room-runtime-nodeps:$(ROOM_LIBS_PATH)/room-runtime-2.0.0-alpha1.aar \
- bt-antlr4-nodeps:$(COMMON_LIBS_PATH)/org/antlr/antlr4/4.5.3/antlr4-4.5.3.jar \
- bt-apache-commons-codec-nodeps:$(COMMON_LIBS_PATH)/org/eclipse/tycho/tycho-bundles-external/0.18.1/eclipse/plugins/org.apache.commons.codec_1.4.0.v201209201156.jar \
- bt-auto-common-nodeps:$(COMMON_LIBS_PATH)/com/google/auto/auto-common/0.9/auto-common-0.9.jar \
- bt-javapoet-nodeps:$(COMMON_LIBS_PATH)/com/squareup/javapoet/1.8.0/javapoet-1.8.0.jar \
- bt-kotlin-metadata-nodeps:$(COMMON_LIBS_PATH)/me/eugeniomarletti/kotlin-metadata/1.2.1/kotlin-metadata-1.2.1.jar \
- bt-sqlite-jdbc-nodeps:$(COMMON_LIBS_PATH)/org/xerial/sqlite-jdbc/3.20.1/sqlite-jdbc-3.20.1.jar \
- bt-jetbrain-nodeps:../../../../../prebuilts/tools/common/m2/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar
-
-include $(BUILD_HOST_PREBUILT)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 92ecae6..5faf095 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WRITE_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+ <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.NFC_HANDOVER_STATUS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
@@ -45,6 +46,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.NETWORK_FACTORY" />
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
@@ -224,12 +227,10 @@
android:excludeFromRecents="true"
android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
android:enabled="@bool/profile_supported_pbap">
- <intent-filter>
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
</activity>
<service
android:process="@string/process"
+ android:permission="android.permission.BLUETOOTH_PRIVILEGED"
android:name=".pbap.BluetoothPbapService"
android:enabled="@bool/profile_supported_pbap" >
<intent-filter>
diff --git a/AndroidManifest_test.xml b/AndroidManifest_test.xml
deleted file mode 100644
index 8529ba5..0000000
--- a/AndroidManifest_test.xml
+++ /dev/null
@@ -1,106 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.bluetooth"
- android:sharedUserId="@string/sharedUserId">
- <!-- Allows access to the Bluetooth Share Manager -->
- <permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE"
- android:label="@string/permlab_bluetoothShareManager"
- android:description="@string/permdesc_bluetoothShareManager"
- android:protectionLevel="signature" />
- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
- <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" />
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.BLUETOOTH" />
- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.READ_CONTACTS" />
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
- <uses-permission android:name="android.permission.WRITE_SETTINGS" />
- <application
- android:icon="@mipmap/bt_share"
- android:label="@string/app_name">
- <uses-library android:name="javax.obex" />
- <activity android:name=".opp.TestActivity" android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <provider android:name=".opp.BluetoothOppProvider"
- android:authorities="com.android.bluetooth.opp"
- android:process="@string/process">
- <path-permission
- android:path="/btopp"
- android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
- </provider>
- <service android:process="@string/process"
- android:name=".opp.BluetoothOppService"
- android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
- <receiver android:process="@string/process"
- android:name=".opp.BluetoothOppReceiver">
- <intent-filter>
- <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
- <action android:name="android.intent.action.BOOT_COMPLETED" />
- </intent-filter>
- </receiver>
- <activity android:name=".opp.BluetoothOppLauncherActivity"
- android:process="@string/process"
- android:theme="@*android:style/Theme.Material.DayNight.Dialog"
- android:label="@string/bt_share_picker_label">
- <intent-filter>
- <action android:name="android.intent.action.SEND" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="image/*" />
- <data android:mimeType="video/*" />
- <data android:mimeType="audio/*" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.SEND_MULTIPLE" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="image/*" />
- <data android:mimeType="video/*" />
- <data android:mimeType="x-mixmedia/*" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.btopp.intent.action.OPEN" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/vnd.android.btopp" />
- </intent-filter>
- </activity>
- <activity android:name=".opp.BluetoothOppBtEnableActivity"
- android:process="@string/process">
- </activity>
- <activity android:name=".opp.BluetoothOppBtErrorActivity"
- android:process="@string/process">
- </activity>
- <activity android:name=".opp.BluetoothOppBtEnablingActivity"
- android:process="@string/process"
- android:theme="@*android:style/Theme.Material.DayNight.Dialog">
- </activity>
- <activity android:name=".opp.BluetoothOppIncomingFileConfirmActivity"
- android:process="@string/process">
- </activity>
- <activity android:name=".opp.BluetoothOppTransferActivity"
- android:process="@string/process">
- </activity>
- <activity android:name=".pbap.BluetoothPbapActivity"
- android:process="@string/process"
- android:label=" "
- android:theme="@*android:style/Theme.Material.DayNight.Dialog.Alert">
- <intent-filter>
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
- <service android:process="@string/process"
- android:name=".pbap.BluetoothPbapService" >
- <action android:name="android.bluetooth.IBluetoothPbap"/>
- </service>
- <receiver android:process="@string/process"
- android:name=".pbap.BluetoothPbapReceiver">
- <intent-filter>
- <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/>
- </intent-filter>
- </receiver>
- </application>
-</manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
deleted file mode 100644
index b84e1b6..0000000
--- a/CleanSpec.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2007 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.
-#
-
-# If you don't need to do a full clean build but would like to touch
-# a file or delete some intermediate files, add a clean step to the end
-# of the list. These steps will only be run once, if they haven't been
-# run before.
-#
-# E.g.:
-# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
-# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
-#
-# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
-# files that are missing or have been moved.
-#
-# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
-# Use $(OUT_DIR) to refer to the "out" directory.
-#
-# If you need to re-do something that's already mentioned, just copy
-# the command and add it to the bottom of the list. E.g., if a change
-# that you made last week required touching a file and a change you
-# made today requires touching the same file, just copy the old
-# touch step and add it to the end of the list.
-#
-# ************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
-# ************************************************
-
-# For example:
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
-#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
-#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
-
-# ************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
-# ************************************************
diff --git a/jni/Android.bp b/jni/Android.bp
deleted file mode 100644
index e0034b1..0000000
--- a/jni/Android.bp
+++ /dev/null
@@ -1,41 +0,0 @@
-cc_library_shared {
- name: "libbluetooth_jni",
- compile_multilib: "first",
- srcs: [
- "com_android_bluetooth_btservice_AdapterService.cpp",
- "com_android_bluetooth_hfp.cpp",
- "com_android_bluetooth_hfpclient.cpp",
- "com_android_bluetooth_a2dp.cpp",
- "com_android_bluetooth_a2dp_sink.cpp",
- "com_android_bluetooth_avrcp_controller.cpp",
- "com_android_bluetooth_avrcp_target.cpp",
- "com_android_bluetooth_hid_host.cpp",
- "com_android_bluetooth_hid_device.cpp",
- "com_android_bluetooth_hearing_aid.cpp",
- "com_android_bluetooth_pan.cpp",
- "com_android_bluetooth_gatt.cpp",
- "com_android_bluetooth_sdp.cpp",
- ],
- header_libs: ["libbluetooth_headers"],
- include_dirs: [
- "system/bt/types",
- ],
- shared_libs: [
- "libbase",
- "libchrome",
- "libnativehelper",
- "liblog",
- ],
- static_libs: [
- "libbluetooth-types",
- ],
- cflags: [
- "-Wall",
- "-Werror",
- "-Wextra",
- "-Wno-unused-parameter",
- ],
- sanitize: {
- scs: true,
- },
-}
diff --git a/jni/com_android_bluetooth_a2dp.cpp b/jni/com_android_bluetooth_a2dp.cpp
index 7ca0aa5..3e155c7 100644
--- a/jni/com_android_bluetooth_a2dp.cpp
+++ b/jni/com_android_bluetooth_a2dp.cpp
@@ -259,7 +259,8 @@
static void initNative(JNIEnv* env, jobject object,
jint maxConnectedAudioDevices,
- jobjectArray codecConfigArray) {
+ jobjectArray codecConfigArray,
+ jobjectArray codecOffloadingArray) {
std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
@@ -305,8 +306,12 @@
std::vector<btav_a2dp_codec_config_t> codec_priorities =
prepareCodecPreferences(env, object, codecConfigArray);
+ std::vector<btav_a2dp_codec_config_t> codec_offloading =
+ prepareCodecPreferences(env, object, codecOffloadingArray);
+
bt_status_t status = sBluetoothA2dpInterface->init(
- &sBluetoothA2dpCallbacks, maxConnectedAudioDevices, codec_priorities);
+ &sBluetoothA2dpCallbacks, maxConnectedAudioDevices, codec_priorities,
+ codec_offloading);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed to initialize Bluetooth A2DP, status: %d", __func__,
status);
@@ -474,7 +479,8 @@
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
- {"initNative", "(I[Landroid/bluetooth/BluetoothCodecConfig;)V",
+ {"initNative",
+ "(I[Landroid/bluetooth/BluetoothCodecConfig;[Landroid/bluetooth/BluetoothCodecConfig;)V",
(void*)initNative},
{"cleanupNative", "()V", (void*)cleanupNative},
{"connectA2dpNative", "([B)Z", (void*)connectA2dpNative},
diff --git a/jni/com_android_bluetooth_avrcp_controller.cpp b/jni/com_android_bluetooth_avrcp_controller.cpp
old mode 100644
new mode 100755
index 112e826..050da06
--- a/jni/com_android_bluetooth_avrcp_controller.cpp
+++ b/jni/com_android_bluetooth_avrcp_controller.cpp
@@ -48,6 +48,7 @@
static jmethodID method_handleSetAddressedPlayerRsp;
static jmethodID method_handleAddressedPlayerChanged;
static jmethodID method_handleNowPlayingContentChanged;
+static jmethodID method_onAvailablePlayerChanged;
static jclass class_MediaBrowser_MediaItem;
static jclass class_AvrcpPlayer;
@@ -719,6 +720,31 @@
sCallbacksObj, method_handleNowPlayingContentChanged, addr.get());
}
+static void btavrcp_available_player_changed_callback (
+ const RawAddress& bd_addr) {
+ ALOGI("%s", __func__);
+
+ std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);
+
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbacksObj) {
+ ALOGE("%s: sCallbacksObj is null", __func__);
+ return;
+ }
+ if (!sCallbackEnv.valid()) return;
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Failed to allocate a new byte array", __func__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(
+ sCallbacksObj, method_onAvailablePlayerChanged, addr.get());
+}
+
static btrc_ctrl_callbacks_t sBluetoothAvrcpCallbacks = {
sizeof(sBluetoothAvrcpCallbacks),
btavrcp_passthrough_response_callback,
@@ -738,7 +764,8 @@
btavrcp_set_browsed_player_callback,
btavrcp_set_addressed_player_callback,
btavrcp_addressed_player_changed_callback,
- btavrcp_now_playing_content_changed_callback};
+ btavrcp_now_playing_content_changed_callback,
+ btavrcp_available_player_changed_callback};
static void classInitNative(JNIEnv* env, jclass clazz) {
method_handlePassthroughRsp =
@@ -804,6 +831,8 @@
env->GetMethodID(clazz, "handleAddressedPlayerChanged", "([BI)V");
method_handleNowPlayingContentChanged =
env->GetMethodID(clazz, "handleNowPlayingContentChanged", "([B)V");
+ method_onAvailablePlayerChanged =
+ env->GetMethodID(clazz, "onAvailablePlayerChanged", "([B)V");
ALOGI("%s: succeeds", __func__);
}
diff --git a/lib/room/annotation-1.0.0-beta01.jar b/lib/room/annotation-1.0.0-beta01.jar
deleted file mode 100644
index 124f128..0000000
--- a/lib/room/annotation-1.0.0-beta01.jar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-common-2.0.0-beta01.jar b/lib/room/room-common-2.0.0-beta01.jar
deleted file mode 100644
index 931d4aa..0000000
--- a/lib/room/room-common-2.0.0-beta01.jar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-compiler-2.0.0-beta01.jar b/lib/room/room-compiler-2.0.0-beta01.jar
deleted file mode 100644
index 623a07b..0000000
--- a/lib/room/room-compiler-2.0.0-beta01.jar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-migration-2.0.0-beta01.jar b/lib/room/room-migration-2.0.0-beta01.jar
deleted file mode 100644
index 04bca6f..0000000
--- a/lib/room/room-migration-2.0.0-beta01.jar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-runtime-2.0.0-alpha1.aar b/lib/room/room-runtime-2.0.0-alpha1.aar
deleted file mode 100644
index 053188b..0000000
--- a/lib/room/room-runtime-2.0.0-alpha1.aar
+++ /dev/null
Binary files differ
diff --git a/lib/room/room-testing-2.0.0-alpha1.aar b/lib/room/room-testing-2.0.0-alpha1.aar
deleted file mode 100644
index bb6e62a..0000000
--- a/lib/room/room-testing-2.0.0-alpha1.aar
+++ /dev/null
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 060fd90..f011684 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-oudio is ontkoppel"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-oudio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Lêers groter as 4 GB kan nie oorgedra word nie"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Koppel aan Bluetooth"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index dbf4aad..0d2f87c 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"የብሉቱዝ ኦዲዮ ግንኙነት ተቋርጧል"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"የብሉቱዝ ኦዲዮ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"ከ4 ጊባ በላይ የሆኑ ፋይሎች ሊዛወሩ አይችሉም"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ከብሉቱዝ ጋር ተገናኝ"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 1525a71..5e3127b 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -142,4 +142,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"انقطع الاتصال بالبث الصوتي عبر البلوتوث."</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"بث صوتي عبر البلوتوث"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"يتعذّر نقل الملفات التي يزيد حجمها عن 4 غيغابايت"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"الاتصال ببلوتوث"</string>
</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index fd6ab37..a5f682c 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ব্লুটুথ অডিঅ\'ৰ সৈতে সংযোগ বিচ্ছিন্ন কৰা হ’ল"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ব্লুটুথ অডিঅ\'"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"৪ জি. বি. তকৈ ডাঙৰ ফাইল স্থানান্তৰ কৰিব নোৱাৰি"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ব্লুটুথৰ সৈতে সংযোগ কৰক"</string>
</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 4abf91f..ea55d17 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio ilə bağlantı kəsildi"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB-dən böyük olan faylları köçürmək mümkün deyil"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'a qoşulun"</string>
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 4d999f3..9ad26e1 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -136,4 +136,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Veza sa Bluetooth audijom je prekinuta"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ne mogu da se prenose datoteke veće od 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Poveži sa Bluetooth-om"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 0026ea6..3c9dbd6 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-аўдыя адключана"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-аўдыя"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Немагчыма перадаць файлы, большыя за 4 ГБ"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Падключыцца да Bluetooth"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 1566a37..4b57342 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудиовръзката през Bluetooth е прекратена"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Аудио през Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Файловете с размер над 4 ГБ не могат да бъдат прехвърлени"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Свързване с Bluetooth"</string>
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 352f359..be01649 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ব্লুটুথ অডিওর সংযোগ বিচ্ছিন্ন হয়েছে"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ব্লুটুথ অডিও"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"৪GB থেকে বড় ফটো ট্রান্সফার করা যাবে না"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ব্লুটুথের সাথে কানেক্ট করুন"</string>
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 7f6eca1..3c7dec4 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -136,4 +136,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio je isključen"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nije moguće prenijeti fajlove veće od 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Poveži se na Bluetooth"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 2c6ec73..bddf096 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -18,7 +18,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="permlab_bluetoothShareManager" msgid="311492132450338925">"Accediu al gestor de baixades."</string>
<string name="permdesc_bluetoothShareManager" msgid="8930572979123190223">"Permet que l\'aplicació accedeixi al gestor d\'ús compartit de Bluetooth i que l\'utilitzi per transferir fitxers."</string>
- <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Accés al dispositiu Bluetooth en llista blanca."</string>
+ <string name="permlab_bluetoothWhitelist" msgid="7091552898592306386">"Accés de dispositius Bluetooth en llista blanca."</string>
<string name="permdesc_bluetoothWhitelist" msgid="5494513855192170109">"Permet que l\'aplicació col·loqui temporalment en una llista blanca un dispositiu Bluetooth, cosa que permet que el dispositiu enviï fitxers a aquest dispositiu sense la confirmació de l\'usuari."</string>
<string name="bt_share_picker_label" msgid="6268100924487046932">"Bluetooth"</string>
<string name="unknown_device" msgid="9221903979877041009">"Dispositiu desconegut"</string>
@@ -30,7 +30,7 @@
<string name="bt_enable_line2" msgid="4341936569415937994">"Vols activar el Bluetooth ara?"</string>
<string name="bt_enable_cancel" msgid="1988832367505151727">"Cancel·la"</string>
<string name="bt_enable_ok" msgid="3432462749994538265">"Activa"</string>
- <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Transferència del fitxer"</string>
+ <string name="incoming_file_confirm_title" msgid="8139874248612182627">"Transferència de fitxers"</string>
<string name="incoming_file_confirm_content" msgid="2752605552743148036">"Acceptes el fitxer entrant?"</string>
<string name="incoming_file_confirm_cancel" msgid="2973321832477704805">"Rebutja"</string>
<string name="incoming_file_confirm_ok" msgid="281462442932231475">"Accepta"</string>
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Àudio per Bluetooth desconnectat"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Àudio per Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No es poden transferir fitxers més grans de 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connecta el Bluetooth"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 3e76d2b..9adb80c 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth Audio – odpojeno"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Soubory větší než 4 GB nelze přenést"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Připojit k Bluetooth"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 25819a1..25cf3d7 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-lyden blev afbrudt"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-lyd"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"File, der er større end 4 GB, kan ikke overføres"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Opret forbindelse til Bluetooth"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index c9e6554..4ffe470 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-Audio-Verbindung aufgehoben"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Dateien mit mehr als 4 GB können nicht übertragen werden"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Mit Bluetooth verbinden"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 1506bd2..e249a38 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Ο ήχος Bluetooth αποσυνδέθηκε"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Ήχος Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Δεν είναι δυνατή η μεταφορά αρχείων που ξεπερνούν τα 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Σύνδεση σε Bluetooth"</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index e549cb6..07202ef 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index ddca497..8dcbab4 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4 GB cannot be transferred"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 02178d4..8bbc66c 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio disconnected"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Files bigger than 4GB cannot be transferred"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connect to Bluetooth"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 4a4eaaa..eb95310 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth desconectado"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No se pueden transferir los archivos de más de 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectarse a Bluetooth"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index a9e448c..a67ba6b 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -122,7 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"Abrir"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"Borrar de la lista"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"Borrar"</string>
- <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Está Sonando"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Reproduciendo"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Guardar"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Cancelar"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Selecciona las cuentas que quieras compartir por Bluetooth. Tendrás que aceptar cualquier acceso a las cuentas al establecer conexión."</string>
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio por Bluetooth desconectado"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio por Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"No se pueden transferir archivos de más de 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectarse a un dispositivo Bluetooth"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index dbc425a..334174f 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetoothi heli ühendus on katkestatud"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetoothi heli"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Faile, mis on üle 4 GB, ei saa üle kanda"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Ühenda Bluetoothiga"</string>
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index cc6e417..72e6096 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Deskonektatu da Bluetooth bidezko audioa"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth bidezko audioa"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ezin dira transferitu 4 GB baino gehiagoko fitxategiak"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Konektatu Bluetooth-era"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index c95558e..e2c1b75 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ارتباط بلوتوث صوتی قطع شد"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"بلوتوث صوتی"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"فایلهای بزرگتر از ۴ گیگابایت نمیتوانند منتقل شوند"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"اتصال به بلوتوث"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 0fbc55c..f1792c6 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-ääni katkaistu"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-ääni"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Yli 4 Gt:n kokoisia tiedostoja ei voi siirtää."</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Muodosta Bluetooth-yhteys"</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index b881cfc..4a62f98 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth déconnecté"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Les fichiers dépassant 4 Go ne peuvent pas être transférés"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connexion au Bluetooth"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 06fe209..e0caaee 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth déconnecté"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Impossible de transférer les fichiers supérieurs à 4 Go"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Se connecter au Bluetooth"</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 9dedefd..1f2b1d0 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Desconectouse o audio por Bluetooth"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio por Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Non se poden transferir ficheiros de máis de 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectar ao Bluetooth"</string>
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index d6e1000..4195292 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"બ્લૂટૂથ ઑડિઓ ડિસ્કનેક્ટ થયું"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"બ્લૂટૂથ ઑડિઓ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB કરતા મોટી ફાઇલ ટ્રાન્સફર કરી શકાતી નથી"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"બ્લૂટૂથ સાથે કનેક્ટ કરો"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index d01d137..cb0bb5f 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लूटूथ ऑडियो डिसकनेक्ट किया गया"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ब्लूटूथ ऑडियो"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 जीबी से बड़ी फ़ाइलें ट्रांसफ़र नहीं की जा सकतीं"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लूटूथ से कनेक्ट करें"</string>
</resources>
diff --git a/res/values-hi/test_strings.xml b/res/values-hi/test_strings.xml
index f8379b8..52e0e1a 100644
--- a/res/values-hi/test_strings.xml
+++ b/res/values-hi/test_strings.xml
@@ -3,7 +3,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="6006644116867509664">"ब्लूटूथ"</string>
<string name="insert_record" msgid="1450997173838378132">"रिकॉर्ड सम्मिलित करें"</string>
- <string name="update_record" msgid="2480425402384910635">"रिकॉर्ड की दुबारा पूछें"</string>
+ <string name="update_record" msgid="2480425402384910635">"रिकॉर्ड की पुष्टि करें"</string>
<string name="ack_record" msgid="6716152390978472184">"रिकॉर्ड अभिस्वीकृत करें"</string>
<string name="deleteAll_record" msgid="4383349788485210582">"सभी रिकॉर्ड मिटाएं"</string>
<string name="ok_button" msgid="6519033415223065454">"ठीक है"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index d05c7b3..581e1a8 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -136,4 +136,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Veza Bluetooth Audija prekinuta"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Datoteke veće od 4 GB ne mogu se prenijeti"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Povezivanje s Bluetoothom"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 4fa442f..ff93e44 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audió leválasztva"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audió"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"A 4 GB-nál nagyobb fájlokat nem lehet átvinni"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Csatlakozás Bluetooth-eszközhöz"</string>
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index bf6fbf3..1fb6d74 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth աուդիոն անջատված է"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth աուդիո"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 ԳԲ-ից մեծ ֆայլերը հնարավոր չէ փոխանցել"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Միանալ Bluetooth-ին"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 15722da..01c4fe1 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth audio terputus"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"File yang berukuran lebih dari 4GB tidak dapat ditransfer"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Hubungkan ke Bluetooth"</string>
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 6246389..65db6e9 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-hljóð aftengt"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-hljóð"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Ekki er hægt að flytja skrár sem eru stærri en 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Tengjast við Bluetooth"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e4a486f..27cefda 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth disconnesso"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Impossibile trasferire file con dimensioni superiori a 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Connettiti a Bluetooth"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 7fe6a4f..11446ff 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -69,7 +69,7 @@
<string name="upload_succ_ok" msgid="7705428476405478828">"אישור"</string>
<string name="upload_fail_line1" msgid="7899394672421491701">"הקובץ לא נשלח אל <xliff:g id="RECIPIENT">%1$s</xliff:g>."</string>
<string name="upload_fail_line1_2" msgid="2108129204050841798">"קובץ: <xliff:g id="FILE">%1$s</xliff:g>"</string>
- <string name="upload_fail_ok" msgid="5807702461606714296">"נסה שוב"</string>
+ <string name="upload_fail_ok" msgid="5807702461606714296">"כדאי לנסות שוב"</string>
<string name="upload_fail_cancel" msgid="9118496285835687125">"סגור"</string>
<string name="bt_error_btn_ok" msgid="5965151173011534240">"אישור"</string>
<string name="unknown_file" msgid="6092727753965095366">"קובץ לא ידוע"</string>
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"אודיו Bluetooth מנותק"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"אודיו Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"לא ניתן להעביר קבצים שגדולים מ-GB 4"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"התחברות באמצעות Bluetooth"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 6171772..7688d48 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth オーディオは接続を解除されています"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth オーディオ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB を超えるファイルは転送できません"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth に接続する"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 4983eaa..92b6329 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth აუდიო გათიშულია"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth აუდიო"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 გბაიტზე დიდი მოცულობის ფაილების გადატანა ვერ მოხერხდება"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-თან დაკავშირება"</string>
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 82de8a6..811187f 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth дыбысы ажыратылды"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth aудиосы"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Көлемі 4 ГБ-тан асатын файлдар тасымалданбайды"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-ге қосылу"</string>
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index f4b7e7a..1db467c 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -122,7 +122,7 @@
<string name="transfer_menu_open" msgid="3368984869083107200">"បើក"</string>
<string name="transfer_menu_clear" msgid="5854038118831427492">"សម្អាតពីបញ្ជី"</string>
<string name="transfer_clear_dlg_title" msgid="2953444575556460386">"សម្អាត"</string>
- <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Now Playing"</string>
+ <string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"ឥឡូវកំពុងចាក់"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"រក្សាទុក"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"បោះបង់"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"ជ្រើសគណនីដែលអ្នកចង់ចែករំលែកតាមរយៈប៊្លូធូស។ អ្នកនៅតែត្រូវទទួលយកលទ្ធភាពចូលដំណើរការទាំងឡាយទៅកាន់គណនីនេះដដែល នៅពេលភ្ជាប់។"</string>
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"សំឡេងប៊្លូធូសត្រូវបានផ្តាច់"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"សំឡេងប៊្លូធូស"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"ឯកសារដែលមានទំហំធំជាង 4 GB មិនអាចផ្ទេរបានទេ"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ភ្ជាប់ប៊្លូធូស"</string>
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 0f0e06f..1d87823 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೊ ಸಂಪರ್ಕ ಕಡಿತಗೊಂಡಿದೆ"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ಬ್ಲೂಟೂತ್ ಆಡಿಯೊ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ಗಿಂತ ದೊಡ್ಡದಾದ ಫೈಲ್ಗಳನ್ನು ವರ್ಗಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ಬ್ಲೂಟೂತ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f5eeca9..3977271 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"블루투스 오디오가 연결 해제됨"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"블루투스 오디오"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB보다 큰 파일은 전송할 수 없습니다"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"블루투스에 연결"</string>
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 0ed4aa9..9af9bc8 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -125,7 +125,7 @@
<string name="bluetooth_a2dp_sink_queue_name" msgid="6864149958708669766">"Азыр эмне ойноп жатат?"</string>
<string name="bluetooth_map_settings_save" msgid="7635491847388074606">"Сактоо"</string>
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"Жокко чыгаруу"</string>
- <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын каттоо эсептерине кирүү мүмкүнчүлүгүн ырастап турушуңуз керек."</string>
+ <string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Bluetooth аркылуу бөлүшө турган каттоо эсептерин тандаңыз. Туташкан сайын аккаунттарына кирүү мүмкүнчүлүгүн ырастап турушуңуз керек."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Калган көзөнөктөр:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Колдонмонун сүрөтчөсү"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Bluetooth билдирүү бөлүшүү жөндөөлөрү"</string>
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth аудио ажыратылды"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth аудио"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4Гб чоң файлдарды өткөрүү мүмкүн эмес"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'га туташуу"</string>
</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index bebe622..3257841 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ຕັດການເຊື່ອມຕໍ່ສຽງ Bluetooth ແລ້ວ"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ສຽງ Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"ບໍ່ສາມາດໂອນຍ້າຍໄຟລ໌ທີ່ໃຫຍກວ່າ 4GB ໄດ້"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ເຊື່ອມຕໍ່ກັບ Bluetooth"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 5d0f759..42562f4 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"„Bluetooth“ garsas atjungtas"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"„Bluetooth“ garsas"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Negalima perkelti didesnių nei 4 GB failų"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Prisijungti prie „Bluetooth“"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 062a019..6edd984 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -136,4 +136,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Pārtraukts savienojums ar Bluetooth audio"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nevar pārsūtīt failus, kas lielāki par 4 GB."</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Izveidot savienojumu ar Bluetooth"</string>
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 011c39d..5045b56 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудиото преку Bluetooth е исклучено"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Аудио преку Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не може да се пренесуваат датотеки поголеми од 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Поврзи се со Bluetooth"</string>
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 08e907f..40e311f 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -132,4 +132,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth ഓഡിയോ വിച്ഛേദിച്ചു"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth ഓഡിയോ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB-യിൽ കൂടുതലുള്ള ഫയലുകൾ കൈമാറാനാവില്ല"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-ലേക്ക് കണക്റ്റ് ചെയ്യുക"</string>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index aec448c..60f238a 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth аудиог салгасан"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Аудио"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4ГБ-с дээш хэмжээтэй файлыг шилжүүлэх боломжгүй"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth-тэй холбогдох"</string>
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index a7c0f1e..43e0a54 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -127,11 +127,12 @@
<string name="bluetooth_map_settings_cancel" msgid="9205350798049865699">"रद्द करा"</string>
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"तुम्ही ब्लूटूथद्वारे शेअर करू इच्छित असलेली खाती निवडा. कनेक्ट करताना अद्याप तुम्ही खात्यांमधील कोणताही अॅक्सेस स्वीकारण्याची आवश्यकता आहे."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"स्लॉट शिल्लक:"</string>
- <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"अॅप्लिकेशन आयकन"</string>
+ <string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"ॲप्लिकेशन आयकन"</string>
<string name="bluetooth_map_settings_title" msgid="7420332483392851321">"ब्लूटूथ मेसेज सामायिकरण सेटिंग्ज"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"खाते निवडू शकत नाही. 0 स्लॉट शिल्लक"</string>
<string name="bluetooth_connected" msgid="6718623220072656906">"ब्लूटूथ ऑडिओ कनेक्ट केला"</string>
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लूटूथ ऑडिओ डिस्कनेक्ट केला"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ब्लूटूथ ऑडिओ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB हून मोठ्या फायली ट्रान्सफर करता येणार नाहीत"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लूटूथशी कनेक्ट करा"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index ab9c7a9..a0071ca 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio Bluetooth diputuskan sambungannya"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Fail lebih besar daripada 4GB tidak boleh dipindahkan"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Sambung ke Bluetooth"</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 7e7763a..eb35b0f 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ဘလူးတုသ်အသံ မချိတ်ဆက်ထားပါ"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ဘလူးတုသ် အသံ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ထက်ပိုကြီးသည့် ဖိုင်များကို လွှဲပြောင်းမရနိုင်ပါ"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ဘလူးတုသ်သို့ ချိတ်ဆက်ရန်"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index e0418b1..d6f9e77 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-lyd er frakoblet"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-lyd"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Filer som er større enn 4 GB, kan ikke overføres"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Koble til Bluetooth"</string>
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 639df71..6f9ab58 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ब्लुटुथ सम्बन्धी अडियो यन्त्रलाई विच्छेद गरियो"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ब्लुटुथको अडियो"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"४ जि.बि. भन्दा ठूला फाइलहरूलाई स्थानान्तरण गर्न सकिँदैन"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ब्लुटुथमा जोड्नुहोस्"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 9207378..8f49620 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-audio ontkoppeld"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Bestanden groter dan 4 GB kunnen niet worden overgedragen"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Verbinding maken met bluetooth"</string>
</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 8819773..94ad041 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ବ୍ଲୁଟୂଥ୍ ଅଡିଓ ବିଚ୍ଛିନ୍ନ କରାଗଲା"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ବ୍ଲୁଟୂଥ୍ ଅଡିଓ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GBରୁ ବଡ଼ ଫାଇଲ୍ଗୁଡ଼ିକୁ ଟ୍ରାନ୍ସଫର୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ବ୍ଲୁଟୁଥ୍ ସହ ସଂଯୋଗ କରନ୍ତୁ"</string>
</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 07cab6a..71c771b 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ਬਲੂਟੁੱਥ ਆਡੀਓ ਡਿਸਕਨੈਕਟ ਕੀਤੀ ਗਈ"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"ਬਲੂਟੁੱਥ ਆਡੀਓ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB ਤੋਂ ਵਧੇਰੇ ਵੱਡੀਆਂ ਫ਼ਾਈਲਾਂ ਨੂੰ ਟ੍ਰਾਂਸਫ਼ਰ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"ਬਲੂਟੁੱਥ ਨਾਲ ਕਨੈਕਟ ਕਰੋ"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 8c4869f..3cfbe0d 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Dźwięk Bluetooth odłączony"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Dźwięk Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Nie można przenieść plików przekraczających 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Nawiązywanie połączeń przez Bluetooth"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 09e6781..9eae5c9 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Áudio Bluetooth desligado"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Áudio Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Não é possível transferir os ficheiros com mais de 4 GB."</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Ligar ao Bluetooth"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 8472432..b4a775e 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Áudio Bluetooth desconectado"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Áudio Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Não é possível transferir arquivos maiores que 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectar ao Bluetooth"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 67f117d..fadc4a6 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -136,4 +136,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Audio prin Bluetooth deconectat"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audio prin Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Fișierele mai mari de 4 GB nu pot fi transferate"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Conectați-vă la Bluetooth"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 87c1492..6625b83 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Звук через Bluetooth отключен"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Звук через Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Можно перенести только файлы размером до 4 ГБ."</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Подключиться по Bluetooth"</string>
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index ed6e355..bbe1e00 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"බ්ලූටූත් ශ්රව්යය විසන්ධි කරන ලදී"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"බ්ලූටූත් ශ්රව්යය"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GBට වඩා විශාල ගොනු මාරු කළ නොහැකිය"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"බ්ලූටූත් වෙත සබඳින්න"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index d189d0d..e28a792 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Rozhranie Bluetooth Audio je odpojené"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Súbory väčšie ako 4 GB sa nedajú preniesť"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Pripojiť k zariadeniu Bluetooth"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 912ba37..d681fb2 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Zvok prek Bluetootha ni povezan"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Zvok prek Bluetootha"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Datotek, večjih od 4 GB, ni mogoče prenesti"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Povezovanje z Bluetoothom"</string>
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 1a95dcd..5d1aff3 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Audioja e \"bluetooth-it\" e shkëputur"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Audioja e \"bluetooth-it\""</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Skedarët më të mëdhenj se 4 GB nuk mund të transferohen"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Lidhu me Bluetooth"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 6bdf67a..25fc24f 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -136,4 +136,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Веза са Bluetooth аудијом је прекинута"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth аудио"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не могу да се преносе датотеке веће од 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Повежи са Bluetooth-ом"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 7c1609f..f243475 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth-ljud är frånkopplat"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth-ljud"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Det går inte att överföra filer som är större än 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Anslut till Bluetooth"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 39f803e..edc03d9 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Imeondoa sauti ya Bluetooth"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Sauti ya Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Haiwezi kutuma faili zinazozidi GB 4"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Unganisha kwenye Bluetooth"</string>
</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index e584f9f..67d872f 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"புளூடூத் ஆடியோ துண்டிக்கப்பட்டது"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"புளூடூத் ஆடியோ"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4ஜி.பை.க்கு மேலிருக்கும் ஃபைல்களை இடமாற்ற முடியாது"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"புளூடூத் உடன் இணை"</string>
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 7aaeaa4..e7f7775 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"బ్లూటూత్ ఆడియో డిస్కనెక్ట్ చేయబడింది"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"బ్లూటూత్ ఆడియో"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB కన్నా పెద్ద ఫైళ్లు బదిలీ చేయబడవు"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"బ్లూటూత్కు కనెక్ట్ చేయి"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 8cfec4f..48023cd 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"ยกเลิกการเชื่อมต่อ Bluetooth Audio แล้ว"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"โอนไฟล์ที่มีขนาดใหญ่กว่า 4 GB ไม่ได้"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"เชื่อมต่อบลูทูธ"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 1f50ceb..3820800 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Nadiskonekta ang Bluetooth audio"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Hindi maililipat ang mga file na mas malaki sa 4GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Kumonekta sa Bluetooth"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 71ce9a4..e1a0bfb 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth ses bağlantısı kesildi"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Ses"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GB\'tan büyük dosyalar aktarılamaz"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetooth\'a bağlan"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index e357d13..f7d6fbd 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -138,4 +138,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Аудіо Bluetooth від’єднано"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth Audio"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Не можна перенести файли, більші за 4 ГБ"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Підключитися до Bluetooth"</string>
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index cc13746..e944fb8 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"بلوٹوتھ آڈیو غیر منسلک ہے"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"بلوٹوتھ آڈیو"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4GB سے بڑی فائلیں منتقل نہیں کی جا سکتیں"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"بلوٹوتھ سے منسلک کریں"</string>
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index dac670b..cadb8be 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Bluetooth orqali ovoz o‘chirildi"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Bluetooth orqali ovoz"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"4 GBdan katta hajmli videolar o‘tkazilmaydi"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Bluetoothga ulanish"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 4a40dfe..e0653c0 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -128,10 +128,11 @@
<string name="bluetooth_map_settings_intro" msgid="6482369468223987562">"Chọn tài khoản mà bạn muốn chia sẻ qua Bluetooth. Bạn vẫn phải chấp nhận mọi quyền truy cập vào tài khoản khi kết nối."</string>
<string name="bluetooth_map_settings_count" msgid="4557473074937024833">"Số khe cắm còn lại:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="7105805610929114707">"Biểu tượng ứng dụng"</string>
- <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Cài đặt chia sẻ thư qua Bluetooth"</string>
+ <string name="bluetooth_map_settings_title" msgid="7420332483392851321">"Cài đặt cách chia sẻ thư qua Bluetooth"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="1796029082612965251">"Không chọn được tài khoản. Còn lại 0 khe cắm"</string>
<string name="bluetooth_connected" msgid="6718623220072656906">"Đã kết nối âm thanh Bluetooth"</string>
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Đã ngắt kết nối âm thanh Bluetooth"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Âm thanh Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Không thể chuyển những tệp lớn hơn 4 GB"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Kết nối với Bluetooth"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 4898537..a5f6d2d 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"蓝牙音频已断开连接"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"蓝牙音频"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"无法传输 4GB 以上的文件"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"连接到蓝牙"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index dfb352a..2881ffe 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"已與藍牙音訊解除連接"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"藍牙音訊"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"無法轉移 4 GB 以上的檔案"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"連接藍牙"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 046dfc6..c8ece01 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"已中斷與藍牙音訊的連線"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"藍牙音訊"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"無法轉移大於 4GB 的檔案"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"使用藍牙連線"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 1bc0fe6..da08ed2 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -134,4 +134,5 @@
<string name="bluetooth_disconnected" msgid="3318303728981478873">"Umsindo we-Bluetooth unqanyuliwe"</string>
<string name="a2dp_sink_mbs_label" msgid="7566075853395412558">"Umsindo we-Bluetooth"</string>
<string name="bluetooth_opp_file_limit_exceeded" msgid="8894450394309084519">"Amafayela amakhulu kuno-4GB awakwazi ukudluliselwa"</string>
+ <string name="bluetooth_connect_action" msgid="4009848433321657090">"Xhumeka ku-Bluetooth"</string>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0204f98..5b2ad94 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -228,13 +228,13 @@
<string name="transfer_clear_dlg_title">Clear</string>
<!-- Do not translate. file name used for sharing. -->
- <string name="bluetooth_share_file_name" translate="false">bluetooth_content_share</string>
+ <string name="bluetooth_share_file_name" translatable="false">bluetooth_content_share</string>
<!-- Used to run Bluetooth.apk in another process if needed -->
<!-- Do not translate. android:sharedUserId string of this application. -->
- <string name="sharedUserId" translate="false"><xliff:g id="x" /></string>
+ <string name="sharedUserId" translatable="false"><xliff:g id="x" /></string>
<!-- Do not translate. android:process of this application. -->
- <string name="process" translate="false"><xliff:g id="x" /></string>
+ <string name="process" translatable="false"><xliff:g id="x" /></string>
<string name="bluetooth_a2dp_sink_queue_name">Now Playing</string>
diff --git a/src/com/android/bluetooth/Utils.java b/src/com/android/bluetooth/Utils.java
index cb098ac..d86d289 100644
--- a/src/com/android/bluetooth/Utils.java
+++ b/src/com/android/bluetooth/Utils.java
@@ -16,14 +16,18 @@
package com.android.bluetooth;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.location.LocationManager;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.ParcelUuid;
@@ -31,8 +35,12 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Telephony;
import android.util.Log;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -58,6 +66,25 @@
static final int BD_ADDR_LEN = 6; // bytes
static final int BD_UUID_LEN = 16; // bytes
+ /*
+ * Special characters
+ *
+ * (See "What is a phone number?" doc)
+ * 'p' --- GSM pause character, same as comma
+ * 'n' --- GSM wild character
+ * 'w' --- GSM wait character
+ */
+ public static final char PAUSE = ',';
+ public static final char WAIT = ';';
+
+ private static boolean isPause(char c) {
+ return c == 'p' || c == 'P';
+ }
+
+ private static boolean isToneWait(char c) {
+ return c == 'w' || c == 'W';
+ }
+
public static String getAddressStringFromByte(byte[] address) {
if (address == null || address.length != BD_ADDR_LEN) {
return null;
@@ -71,6 +98,10 @@
return getBytesFromAddress(device.getAddress());
}
+ public static byte[] addressToBytes(String address) {
+ return getBytesFromAddress(address);
+ }
+
public static byte[] getBytesFromAddress(String address) {
int i, j = 0;
byte[] output = new byte[BD_ADDR_LEN];
@@ -254,6 +285,52 @@
Utils.sForegroundUserId = uid;
}
+ public static void enforceBluetoothPermission(Context context) {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BLUETOOTH,
+ "Need BLUETOOTH permission");
+ }
+
+ public static void enforceBluetoothAdminPermission(Context context) {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BLUETOOTH_ADMIN,
+ "Need BLUETOOTH ADMIN permission");
+ }
+
+ public static void enforceBluetoothPrivilegedPermission(Context context) {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH PRIVILEGED permission");
+ }
+
+ public static void enforceLocalMacAddressPermission(Context context) {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.LOCAL_MAC_ADDRESS,
+ "Need LOCAL_MAC_ADDRESS permission");
+ }
+
+ public static void enforceDumpPermission(Context context) {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.DUMP,
+ "Need DUMP permission");
+ }
+
+ public static boolean callerIsSystemOrActiveUser(String tag, String method) {
+ if (!checkCaller()) {
+ Log.w(TAG, method + "() - Not allowed for non-active user and non-system user");
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean callerIsSystemOrActiveOrManagedUser(Context context, String tag, String method) {
+ if (!checkCallerAllowManagedProfiles(context)) {
+ Log.w(TAG, method + "() - Not allowed for non-active user and non-system and non-managed user");
+ return false;
+ }
+ return true;
+ }
+
public static boolean checkCaller() {
int callingUser = UserHandle.getCallingUserId();
int callingUid = Binder.getCallingUid();
@@ -311,7 +388,7 @@
* OP_COARSE_LOCATION is allowed
*/
public static boolean checkCallerHasCoarseLocation(Context context, AppOpsManager appOps,
- String callingPackage, UserHandle userHandle) {
+ String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
if (blockedByLocationOff(context, userHandle)) {
Log.e(TAG, "Permission denial: Location is off.");
return false;
@@ -321,7 +398,8 @@
if (context.checkCallingOrSelfPermission(
android.Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED
- && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+ && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
+ callingFeatureId)) {
return true;
}
@@ -336,7 +414,7 @@
* OP_FINE_LOCATION is allowed
*/
public static boolean checkCallerHasCoarseOrFineLocation(Context context, AppOpsManager appOps,
- String callingPackage, UserHandle userHandle) {
+ String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
if (blockedByLocationOff(context, userHandle)) {
Log.e(TAG, "Permission denial: Location is off.");
return false;
@@ -345,7 +423,8 @@
if (context.checkCallingOrSelfPermission(
android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED
- && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+ && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
+ callingFeatureId)) {
return true;
}
@@ -353,7 +432,8 @@
if (context.checkCallingOrSelfPermission(
android.Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED
- && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+ && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
+ callingFeatureId)) {
return true;
}
@@ -367,7 +447,7 @@
* OP_FINE_LOCATION is allowed
*/
public static boolean checkCallerHasFineLocation(Context context, AppOpsManager appOps,
- String callingPackage, UserHandle userHandle) {
+ String callingPackage, @Nullable String callingFeatureId, UserHandle userHandle) {
if (blockedByLocationOff(context, userHandle)) {
Log.e(TAG, "Permission denial: Location is off.");
return false;
@@ -376,7 +456,8 @@
if (context.checkCallingOrSelfPermission(
android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED
- && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
+ && isAppOppAllowed(appOps, AppOpsManager.OPSTR_FINE_LOCATION, callingPackage,
+ callingFeatureId)) {
return true;
}
@@ -412,7 +493,8 @@
return true;
}
- private static boolean isAppOppAllowed(AppOpsManager appOps, int op, String callingPackage) {
+ private static boolean isAppOppAllowed(AppOpsManager appOps, String op, String callingPackage,
+ @NonNull String callingFeatureId) {
return appOps.noteOp(op, Binder.getCallingUid(), callingPackage)
== AppOpsManager.MODE_ALLOWED;
}
@@ -479,4 +561,65 @@
return DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS")
.withZone(ZoneId.systemDefault()).format(Instant.now());
}
+
+ public static void skipCurrentTag(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ }
+ }
+
+ /**
+ * Converts pause and tonewait pause characters
+ * to Android representation.
+ * RFC 3601 says pause is 'p' and tonewait is 'w'.
+ */
+ public static String convertPreDial(String phoneNumber) {
+ if (phoneNumber == null) {
+ return null;
+ }
+ int len = phoneNumber.length();
+ StringBuilder ret = new StringBuilder(len);
+
+ for (int i = 0; i < len; i++) {
+ char c = phoneNumber.charAt(i);
+
+ if (isPause(c)) {
+ c = PAUSE;
+ } else if (isToneWait(c)) {
+ c = WAIT;
+ }
+ ret.append(c);
+ }
+ return ret.toString();
+ }
+
+ /**
+ * Move a message to the given folder.
+ *
+ * @param context the context to use
+ * @param uri the message to move
+ * @param messageSent if the message is SENT or FAILED
+ * @return true if the operation succeeded
+ */
+ public static boolean moveMessageToFolder(Context context, Uri uri, boolean messageSent) {
+ if (uri == null) {
+ return false;
+ }
+
+ ContentValues values = new ContentValues(3);
+ if (messageSent) {
+ values.put(Telephony.Sms.READ, 1);
+ values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_SENT);
+ } else {
+ values.put(Telephony.Sms.READ, 0);
+ values.put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_FAILED);
+ }
+ values.put(Telephony.Sms.ERROR_CODE, 0);
+
+ return 1 == context.getContentResolver().update(uri, values, null, null);
+ }
}
diff --git a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
index 8127e76..6e0b1c0 100644
--- a/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
+++ b/src/com/android/bluetooth/a2dp/A2dpCodecConfig.java
@@ -17,11 +17,13 @@
package com.android.bluetooth.a2dp;
import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecConfig.CodecPriority;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
+import android.media.AudioManager;
import android.util.Log;
import com.android.bluetooth.R;
@@ -39,22 +41,41 @@
private A2dpNativeInterface mA2dpNativeInterface;
private BluetoothCodecConfig[] mCodecConfigPriorities;
- private int mA2dpSourceCodecPrioritySbc = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
- private int mA2dpSourceCodecPriorityAac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
- private int mA2dpSourceCodecPriorityAptx = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
- private int mA2dpSourceCodecPriorityAptxHd = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
- private int mA2dpSourceCodecPriorityLdac = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ private @CodecPriority int mA2dpSourceCodecPrioritySbc =
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ private @CodecPriority int mA2dpSourceCodecPriorityAac =
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ private @CodecPriority int mA2dpSourceCodecPriorityAptx =
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ private @CodecPriority int mA2dpSourceCodecPriorityAptxHd =
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+ private @CodecPriority int mA2dpSourceCodecPriorityLdac =
+ BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+
+ private BluetoothCodecConfig[] mCodecConfigOffloading = new BluetoothCodecConfig[0];
A2dpCodecConfig(Context context, A2dpNativeInterface a2dpNativeInterface) {
mContext = context;
mA2dpNativeInterface = a2dpNativeInterface;
mCodecConfigPriorities = assignCodecConfigPriorities();
+
+ AudioManager audioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+ if (audioManager == null) {
+ Log.w(TAG, "Can't obtain the codec offloading prefernece from null AudioManager");
+ return;
+ }
+ mCodecConfigOffloading = audioManager.getHwOffloadEncodingFormatsSupportedForA2DP()
+ .toArray(mCodecConfigOffloading);
}
BluetoothCodecConfig[] codecConfigPriorities() {
return mCodecConfigPriorities;
}
+ BluetoothCodecConfig[] codecConfigOffloading() {
+ return mCodecConfigOffloading;
+ }
+
void setCodecConfigPreference(BluetoothDevice device,
BluetoothCodecStatus codecStatus,
BluetoothCodecConfig newCodecConfig) {
diff --git a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
index cbdc28a..6202101 100644
--- a/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
+++ b/src/com/android/bluetooth/a2dp/A2dpNativeInterface.java
@@ -51,7 +51,7 @@
private A2dpNativeInterface() {
mAdapter = BluetoothAdapter.getDefaultAdapter();
if (mAdapter == null) {
- Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+ Log.wtf(TAG, "No Bluetooth Adapter Available");
}
}
@@ -75,8 +75,9 @@
* @param codecConfigPriorities an array with the codec configuration
* priorities to configure.
*/
- public void init(int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities) {
- initNative(maxConnectedAudioDevices, codecConfigPriorities);
+ public void init(int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities,
+ BluetoothCodecConfig[] codecConfigOffloading) {
+ initNative(maxConnectedAudioDevices, codecConfigPriorities, codecConfigOffloading);
}
/**
@@ -205,7 +206,8 @@
// Native methods that call into the JNI interface
private static native void classInitNative();
private native void initNative(int maxConnectedAudioDevices,
- BluetoothCodecConfig[] codecConfigPriorities);
+ BluetoothCodecConfig[] codecConfigPriorities,
+ BluetoothCodecConfig[] codecConfigOffloading);
private native void cleanupNative();
private native boolean connectA2dpNative(byte[] address);
private native boolean disconnectA2dpNative(byte[] address);
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 167fc10..6dd2478 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -17,6 +17,8 @@
package com.android.bluetooth.a2dp;
import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
@@ -30,9 +32,9 @@
import android.media.AudioManager;
import android.os.HandlerThread;
import android.util.Log;
-import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
@@ -40,6 +42,7 @@
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.List;
@@ -72,6 +75,9 @@
private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines =
new ConcurrentHashMap<>();
+ // Protect setActiveDevice() so all invoked is handled squentially
+ private final Object mActiveSwitchingGuard = new Object();
+
// Upper limit of all A2DP devices: Bonded or Connected
private static final int MAX_A2DP_STATE_MACHINES = 50;
// Upper limit of all A2DP devices that are Connected or Connecting
@@ -123,7 +129,8 @@
// Step 5: Initialize native interface
mA2dpNativeInterface.init(mMaxConnectedAudioDevices,
- mA2dpCodecConfig.codecConfigPriorities());
+ mA2dpCodecConfig.codecConfigPriorities(),
+ mA2dpCodecConfig.codecConfigOffloading());
// Step 6: Check if A2DP is in offload mode
mA2dpOffloadEnabled = mAdapterService.isA2dpOffloadEnabled();
@@ -229,12 +236,12 @@
Log.d(TAG, "connect(): " + device);
}
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- Log.e(TAG, "Cannot connect to " + device + " : PRIORITY_OFF");
+ if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN");
return false;
}
- if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
- BluetoothUuid.AudioSink)) {
+ if (!ArrayUtils.contains(mAdapterService.getRemoteUuids(device),
+ BluetoothUuid.A2DP_SINK)) {
Log.e(TAG, "Cannot connect to " + device + " : Remote does not have A2DP Sink UUID");
return false;
}
@@ -356,19 +363,18 @@
+ " : too many connected devices");
return false;
}
- // Check priority and accept or reject the connection.
- int priority = getPriority(device);
+ // Check connectionPolicy and accept or reject the connection.
+ int connectionPolicy = getConnectionPolicy(device);
int bondState = mAdapterService.getBondState(device);
// Allow this connection only if the device is bonded. Any attempt to connect while
// bonding would potentially lead to an unauthorized connection.
if (bondState != BluetoothDevice.BOND_BONDED) {
Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
return false;
- } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
- && priority != BluetoothProfile.PRIORITY_ON
- && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
- // Otherwise, reject the connection if priority is not valid.
- Log.w(TAG, "okToConnect: return false, priority=" + priority);
+ } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ // Otherwise, reject the connection if connectionPolicy is not valid.
+ Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
return false;
}
return true;
@@ -386,8 +392,8 @@
}
synchronized (mStateMachines) {
for (BluetoothDevice device : bondedDevices) {
- if (!BluetoothUuid.isUuidPresent(mAdapterService.getRemoteUuids(device),
- BluetoothUuid.AudioSink)) {
+ if (!ArrayUtils.contains(mAdapterService.getRemoteUuids(device),
+ BluetoothUuid.A2DP_SINK)) {
continue;
}
int connectionState = BluetoothProfile.STATE_DISCONNECTED;
@@ -434,16 +440,16 @@
}
private void removeActiveDevice(boolean forceStopPlayingAudio) {
- BluetoothDevice previousActiveDevice = mActiveDevice;
- synchronized (mStateMachines) {
+ synchronized (mActiveSwitchingGuard) {
+ BluetoothDevice previousActiveDevice = null;
+ synchronized (mStateMachines) {
+ if (mActiveDevice == null) return;
+ previousActiveDevice = mActiveDevice;
+ }
// This needs to happen before we inform the audio manager that the device
// disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
updateAndBroadcastActiveDevice(null);
- if (previousActiveDevice == null) {
- return;
- }
-
// Make sure the Audio Manager knows the previous Active device is disconnected.
// However, if A2DP is still connected and not forcing stop audio for that remote
// device, the user has explicitly switched the output to the local device and music
@@ -456,10 +462,13 @@
mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.A2DP, suppressNoisyIntent, -1);
- // Make sure the Active device in native layer is set to null and audio is off
- if (!mA2dpNativeInterface.setActiveDevice(null)) {
- Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
- + "layer");
+
+ synchronized (mStateMachines) {
+ // Make sure the Active device in native layer is set to null and audio is off
+ if (!mA2dpNativeInterface.setActiveDevice(null)) {
+ Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native "
+ + "layer");
+ }
}
}
}
@@ -497,78 +506,94 @@
*/
public boolean setActiveDevice(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- synchronized (mStateMachines) {
- BluetoothDevice previousActiveDevice = mActiveDevice;
- if (DBG) {
- Log.d(TAG, "setActiveDevice(" + device + "): previous is " + previousActiveDevice);
- }
-
+ synchronized (mActiveSwitchingGuard) {
if (device == null) {
// Remove active device and continue playing audio only if necessary.
removeActiveDevice(false);
return true;
}
- BluetoothCodecStatus codecStatus = null;
- A2dpStateMachine sm = mStateMachines.get(device);
- if (sm == null) {
- Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
- + "no state machine");
- return false;
+ A2dpStateMachine sm = null;
+ BluetoothDevice previousActiveDevice = null;
+ synchronized (mStateMachines) {
+ if (Objects.equals(device, mActiveDevice)) {
+ Log.i(TAG, "setActiveDevice(" + device + "): current is " + mActiveDevice
+ + " no changed");
+ // returns true since the device is activated even double attempted
+ return true;
+ }
+ if (DBG) {
+ Log.d(TAG, "setActiveDevice(" + device + "): current is " + mActiveDevice);
+ }
+ sm = mStateMachines.get(device);
+ if (sm == null) {
+ Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
+ + "no state machine");
+ return false;
+ }
+ if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
+ Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
+ + "device is not connected");
+ return false;
+ }
+ previousActiveDevice = mActiveDevice;
}
- if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
- Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active: "
- + "device is not connected");
- return false;
- }
- if (!mA2dpNativeInterface.setActiveDevice(device)) {
- Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer");
- return false;
- }
- codecStatus = sm.getCodecStatus();
- boolean deviceChanged = !Objects.equals(device, mActiveDevice);
+ // Switch from one A2DP to another A2DP device
+ if (DBG) {
+ Log.d(TAG, "Switch A2DP devices to " + device + " from " + previousActiveDevice);
+ }
// This needs to happen before we inform the audio manager that the device
// disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
updateAndBroadcastActiveDevice(device);
- if (deviceChanged) {
+ // Make sure the Audio Manager knows the previous Active device is disconnected,
+ // and the new Active device is connected.
+ // Also, mute and unmute the output during the switch to avoid audio glitches.
+ boolean wasMuted = false;
+ if (previousActiveDevice != null) {
+ if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
+ mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_MUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
+ wasMuted = true;
+ }
+ mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.A2DP, true, -1);
+ }
+
+ BluetoothDevice newActiveDevice = null;
+ synchronized (mStateMachines) {
+ if (!mA2dpNativeInterface.setActiveDevice(device)) {
+ Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native "
+ + "layer");
+ // Remove active device and stop playing audio.
+ removeActiveDevice(true);
+ return false;
+ }
// Send an intent with the active device codec config
+ BluetoothCodecStatus codecStatus = sm.getCodecStatus();
if (codecStatus != null) {
broadcastCodecConfig(mActiveDevice, codecStatus);
}
- // Make sure the Audio Manager knows the previous Active device is disconnected,
- // and the new Active device is connected.
- // Also, mute and unmute the output during the switch to avoid audio glitches.
- boolean wasMuted = false;
- if (previousActiveDevice != null) {
- if (!mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC)) {
- mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_MUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
- wasMuted = true;
- }
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.A2DP, true, -1);
- }
+ newActiveDevice = mActiveDevice;
+ }
- int rememberedVolume = -1;
- if (mFactory.getAvrcpTargetService() != null) {
- rememberedVolume = mFactory.getAvrcpTargetService()
- .getRememberedVolumeForDevice(mActiveDevice);
- }
-
- mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- mActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
- true, rememberedVolume);
-
- // Inform the Audio Service about the codec configuration
- // change, so the Audio Service can reset accordingly the audio
- // feeding parameters in the Audio HAL to the Bluetooth stack.
- mAudioManager.handleBluetoothA2dpDeviceConfigChange(mActiveDevice);
- if (wasMuted) {
- mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
- }
+ // Tasks of Bluetooth are done, and now restore the AudioManager side.
+ int rememberedVolume = -1;
+ if (mFactory.getAvrcpTargetService() != null) {
+ rememberedVolume = mFactory.getAvrcpTargetService()
+ .getRememberedVolumeForDevice(newActiveDevice);
+ }
+ mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ newActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
+ true, rememberedVolume);
+ // Inform the Audio Service about the codec configuration
+ // change, so the Audio Service can reset accordingly the audio
+ // feeding parameters in the Audio HAL to the Bluetooth stack.
+ mAudioManager.handleBluetoothA2dpDeviceConfigChange(newActiveDevice);
+ if (wasMuted) {
+ mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
}
}
return true;
@@ -592,20 +617,54 @@
}
}
- public boolean setPriority(BluetoothDevice device, int priority) {
+ /**
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
+ Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
- mAdapterService.getDatabase()
- .setProfilePriority(device, BluetoothProfile.A2DP, priority);
- return true;
+ boolean setSuccessfully;
+ setSuccessfully = mAdapterService.getDatabase()
+ .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, connectionPolicy);
+ if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (setSuccessfully
+ && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
+ return setSuccessfully;
}
- public int getPriority(BluetoothDevice device) {
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
return mAdapterService.getDatabase()
- .getProfilePriority(device, BluetoothProfile.A2DP);
+ .getProfileConnectionPolicy(device, BluetoothProfile.A2DP);
}
public boolean isAvrcpAbsoluteVolumeSupported() {
@@ -675,7 +734,8 @@
*/
public void setCodecConfigPreference(BluetoothDevice device,
BluetoothCodecConfig codecConfig) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH_PRIVILEGED permission");
if (DBG) {
Log.d(TAG, "setCodecConfigPreference(" + device + "): "
+ Objects.toString(codecConfig));
@@ -707,7 +767,8 @@
* @hide
*/
public void enableOptionalCodecs(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH_PRIVILEGED permission");
if (DBG) {
Log.d(TAG, "enableOptionalCodecs(" + device + ")");
}
@@ -738,7 +799,8 @@
* @hide
*/
public void disableOptionalCodecs(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH_PRIVILEGED permission");
if (DBG) {
Log.d(TAG, "disableOptionalCodecs(" + device + ")");
}
@@ -761,25 +823,55 @@
mA2dpCodecConfig.disableOptionalCodecs(device, codecStatus.getCodecConfig());
}
- public int getSupportsOptionalCodecs(BluetoothDevice device) {
+ /**
+ * Checks whether optional codecs are supported
+ *
+ * @param device is the remote bluetooth device.
+ * @return whether optional codecs are supported. Possible values are:
+ * {@link OptionalCodecsSupportStatus#OPTIONAL_CODECS_SUPPORTED},
+ * {@link OptionalCodecsSupportStatus#OPTIONAL_CODECS_NOT_SUPPORTED},
+ * {@link OptionalCodecsSupportStatus#OPTIONAL_CODECS_SUPPORT_UNKNOWN}.
+ */
+ public @OptionalCodecsSupportStatus int getSupportsOptionalCodecs(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device);
}
public void setSupportsOptionalCodecs(BluetoothDevice device, boolean doesSupport) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH_PRIVILEGED permission");
int value = doesSupport ? BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED
: BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED;
mAdapterService.getDatabase().setA2dpSupportsOptionalCodecs(device, value);
}
- public int getOptionalCodecsEnabled(BluetoothDevice device) {
+ /**
+ * Checks whether optional codecs are enabled
+ *
+ * @param device is the remote bluetooth device
+ * @return whether the optional codecs are enabled. Possible values are:
+ * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_ENABLED},
+ * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_DISABLED},
+ * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_UNKNOWN}.
+ */
+ public @OptionalCodecsPreferenceStatus int getOptionalCodecsEnabled(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device);
}
- public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ /**
+ * Sets the optional codecs to be set to the passed in value
+ *
+ * @param device is the remote bluetooth device
+ * @param value is the new status for the optional codecs. Possible values are:
+ * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_ENABLED},
+ * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_DISABLED},
+ * {@link OptionalCodecsPreferenceStatus#OPTIONAL_CODECS_PREF_UNKNOWN}.
+ */
+ public void setOptionalCodecsEnabled(BluetoothDevice device,
+ @OptionalCodecsPreferenceStatus int value) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH_PRIVILEGED permission");
if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
&& value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
&& value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
@@ -835,7 +927,7 @@
boolean sameAudioFeedingParameters) {
// Log codec config and capability metrics
BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
- StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_A2DP_CODEC_CONFIG_CHANGED,
mAdapterService.obfuscateAddress(device), codecConfig.getCodecType(),
codecConfig.getCodecPriority(), codecConfig.getSampleRate(),
codecConfig.getBitsPerSample(), codecConfig.getChannelMode(),
@@ -843,7 +935,7 @@
codecConfig.getCodecSpecific3(), codecConfig.getCodecSpecific4());
BluetoothCodecConfig[] codecCapabilities = codecStatus.getCodecsSelectableCapabilities();
for (BluetoothCodecConfig codecCapability : codecCapabilities) {
- StatsLog.write(StatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_A2DP_CODEC_CAPABILITY_CHANGED,
mAdapterService.obfuscateAddress(device), codecCapability.getCodecType(),
codecCapability.getCodecPriority(), codecCapability.getSampleRate(),
codecCapability.getBitsPerSample(), codecCapability.getChannelMode(),
@@ -895,16 +987,16 @@
Log.d(TAG, "updateAndBroadcastActiveDevice(" + device + ")");
}
+ // Make sure volume has been store before device been remove from active.
+ if (mFactory.getAvrcpTargetService() != null) {
+ mFactory.getAvrcpTargetService().volumeDeviceSwitched(device);
+ }
synchronized (mStateMachines) {
- if (mFactory.getAvrcpTargetService() != null) {
- mFactory.getAvrcpTargetService().volumeDeviceSwitched(device);
- }
-
mActiveDevice = device;
}
- StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.A2DP,
- mAdapterService.obfuscateAddress(device));
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED,
+ BluetoothProfile.A2DP, mAdapterService.obfuscateAddress(device));
Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -964,12 +1056,11 @@
if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
return;
}
- if (mFactory.getAvrcpTargetService() != null) {
- mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
- }
-
- removeStateMachine(device);
}
+ if (mFactory.getAvrcpTargetService() != null) {
+ mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
+ }
+ removeStateMachine(device);
}
private void removeStateMachine(BluetoothDevice device) {
@@ -1049,28 +1140,24 @@
if ((device == null) || (fromState == toState)) {
return;
}
- synchronized (mStateMachines) {
- if (toState == BluetoothProfile.STATE_CONNECTED) {
- MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
- }
- // Set the active device if only one connected device is supported and it was connected
- if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) {
- setActiveDevice(device);
- }
- // Check if the active device is not connected anymore
- if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) {
- setActiveDevice(null);
- }
- // Check if the device is disconnected - if unbond, remove the state machine
- if (toState == BluetoothProfile.STATE_DISCONNECTED) {
- int bondState = mAdapterService.getBondState(device);
- if (bondState == BluetoothDevice.BOND_NONE) {
- if (mFactory.getAvrcpTargetService() != null) {
- mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
- }
-
- removeStateMachine(device);
+ if (toState == BluetoothProfile.STATE_CONNECTED) {
+ MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
+ }
+ // Set the active device if only one connected device is supported and it was connected
+ if (toState == BluetoothProfile.STATE_CONNECTED && (mMaxConnectedAudioDevices == 1)) {
+ setActiveDevice(device);
+ }
+ // Check if the active device is not connected anymore
+ if (isActiveDevice(device) && (fromState == BluetoothProfile.STATE_CONNECTED)) {
+ setActiveDevice(null);
+ }
+ // Check if the device is disconnected - if unbond, remove the state machine
+ if (toState == BluetoothProfile.STATE_DISCONNECTED) {
+ if (mAdapterService.getBondState(device) == BluetoothDevice.BOND_NONE) {
+ if (mFactory.getAvrcpTargetService() != null) {
+ mFactory.getAvrcpTargetService().removeStoredVolumeForDevice(device);
}
+ removeStateMachine(device);
}
}
}
@@ -1189,21 +1276,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
A2dpService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
A2dpService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
@Override
@@ -1299,6 +1386,23 @@
public void dump(StringBuilder sb) {
super.dump(sb);
ProfileService.println(sb, "mActiveDevice: " + mActiveDevice);
+ ProfileService.println(sb, "mMaxConnectedAudioDevices: " + mMaxConnectedAudioDevices);
+ if (mA2dpCodecConfig != null) {
+ ProfileService.println(sb, "codecConfigPriorities:");
+ for (BluetoothCodecConfig codecConfig : mA2dpCodecConfig.codecConfigPriorities()) {
+ ProfileService.println(sb, " " + codecConfig.getCodecName() + ": "
+ + codecConfig.getCodecPriority());
+ }
+ ProfileService.println(sb, "mA2dpOffloadEnabled: " + mA2dpOffloadEnabled);
+ if (mA2dpOffloadEnabled) {
+ ProfileService.println(sb, "codecConfigOffloading:");
+ for (BluetoothCodecConfig codecConfig : mA2dpCodecConfig.codecConfigOffloading()) {
+ ProfileService.println(sb, " " + codecConfig);
+ }
+ }
+ } else {
+ ProfileService.println(sb, "mA2dpCodecConfig: null");
+ }
for (A2dpStateMachine sm : mStateMachines.values()) {
sm.dump(sb);
}
diff --git a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index e0df8b2..946b9b4 100644
--- a/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -54,16 +54,17 @@
import android.os.Looper;
import android.os.Message;
import android.util.Log;
-import android.util.StatsLog;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Objects;
import java.util.Scanner;
final class A2dpStateMachine extends StateMachine {
@@ -199,7 +200,7 @@
A2dpStackEvent event = (A2dpStackEvent) message.obj;
log("Disconnected: stack event: " + event);
if (!mDevice.equals(event.device)) {
- Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+ Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -306,7 +307,7 @@
A2dpStackEvent event = (A2dpStackEvent) message.obj;
log("Connecting: stack event: " + event);
if (!mDevice.equals(event.device)) {
- Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+ Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -399,7 +400,7 @@
A2dpStackEvent event = (A2dpStackEvent) message.obj;
log("Disconnecting: stack event: " + event);
if (!mDevice.equals(event.device)) {
- Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+ Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -509,7 +510,7 @@
A2dpStackEvent event = (A2dpStackEvent) message.obj;
log("Connected: stack event: " + event);
if (!mDevice.equals(event.device)) {
- Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+ Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -596,7 +597,7 @@
boolean isConnected() {
synchronized (this) {
- return (getCurrentState() == mConnected);
+ return (getConnectionState() == BluetoothProfile.STATE_CONNECTED);
}
}
@@ -687,7 +688,7 @@
private void broadcastAudioState(int newState, int prevState) {
log("A2DP Playing state : device: " + mDevice + " State:" + audioStateToString(prevState)
+ "->" + audioStateToString(newState));
- StatsLog.write(StatsLog.BLUETOOTH_A2DP_PLAYBACK_STATE_CHANGED, newState);
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_A2DP_PLAYBACK_STATE_CHANGED, newState);
Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
@@ -765,14 +766,27 @@
}
public void dump(StringBuilder sb) {
- ProfileService.println(sb, "mDevice: " + mDevice);
- ProfileService.println(sb, " StateMachine: " + this.toString());
+ boolean isActive = Objects.equals(mDevice, mA2dpService.getActiveDevice());
+ ProfileService.println(sb,
+ "=== A2dpStateMachine for " + mDevice + (isActive ? " (Active) ===" : " ==="));
+ ProfileService.println(sb,
+ " getConnectionPolicy: " + mA2dpService.getConnectionPolicy(mDevice));
+ ProfileService.println(sb, " mConnectionState: " + profileStateToString(mConnectionState)
+ + ", mLastConnectionState: " + profileStateToString(mLastConnectionState));
ProfileService.println(sb, " mIsPlaying: " + mIsPlaying);
+ ProfileService.println(sb,
+ " getSupportsOptionalCodecs: " + mA2dpService.getSupportsOptionalCodecs(mDevice)
+ + ", getOptionalCodecsEnabled: " + mA2dpService.getOptionalCodecsEnabled(mDevice));
synchronized (this) {
if (mCodecStatus != null) {
ProfileService.println(sb, " mCodecConfig: " + mCodecStatus.getCodecConfig());
+ ProfileService.println(sb, " mCodecsSelectableCapabilities:");
+ for (BluetoothCodecConfig config : mCodecStatus.getCodecsSelectableCapabilities()) {
+ ProfileService.println(sb, " " + config);
+ }
}
}
+ ProfileService.println(sb, " StateMachine: " + this.toString());
// Dump the state machine logs
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index 5271cc7..91a5774 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -171,21 +171,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
A2dpSinkService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
A2dpSinkService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
@Override
@@ -224,8 +224,9 @@
Log.d(TAG, " connect device: " + device
+ ", InstanceMap start state: " + sb.toString());
}
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is PRIORITY_OFF");
+ if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ Log.w(TAG, "Connection not allowed: <" + device.getAddress()
+ + "> is CONNECTION_POLICY_FORBIDDEN");
return false;
}
A2dpSinkStateMachine stateMachine = getOrCreateStateMachine(device);
@@ -312,32 +313,47 @@
}
/**
- * Set the priority of the profile.
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
*
- * @param device the remote device
- * @param priority the priority of the profile
- * @return true on success, otherwise false
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
*/
- public boolean setPriority(BluetoothDevice device, int priority) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ enforceCallingOrSelfPermission(
+ BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
+ Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.A2DP_SINK, priority);
+ .setProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK, connectionPolicy);
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
return true;
}
/**
- * Get the priority of the profile.
+ * Get the connection policy of the profile.
*
* @param device the remote device
- * @return priority of the specified device
+ * @return connection policy of the specified device
*/
- public int getPriority(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ public int getConnectionPolicy(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(
+ BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.A2DP_SINK);
+ .getProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK);
}
@@ -362,7 +378,8 @@
}
boolean isA2dpPlaying(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ enforceCallingOrSelfPermission(
+ BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
return mA2dpSinkStreamHandler.isPlaying();
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 77ead16..de47a45 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -15,8 +15,6 @@
*/
package com.android.bluetooth.a2dpsink;
-import static android.bluetooth.BluetoothProfile.PRIORITY_OFF;
-
import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
@@ -30,8 +28,8 @@
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
public class A2dpSinkStateMachine extends StateMachine {
@@ -171,7 +169,8 @@
case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
switch (event.mState) {
case StackEvent.CONNECTION_STATE_CONNECTING:
- if (mService.getPriority(mDevice) == PRIORITY_OFF) {
+ if (mService.getConnectionPolicy(mDevice)
+ == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.w(TAG, "Ignore incoming connection, profile is"
+ " turned off for " + mDevice);
mService.disconnectA2dpNative(mDeviceAddress);
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
index ffd648a..5aa3cbb 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java
@@ -183,8 +183,9 @@
break;
case AUDIO_FOCUS_CHANGE:
+ mAudioFocus = (int) message.obj;
// message.obj is the newly granted audio focus.
- switch ((int) message.obj) {
+ switch (mAudioFocus) {
case AudioManager.AUDIOFOCUS_GAIN:
removeMessages(DELAYED_PAUSE);
// Begin playing audio, if we paused the remote, send a play now.
@@ -245,12 +246,9 @@
*/
private void requestAudioFocusIfNone() {
if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");
- if (mAudioFocus == AudioManager.AUDIOFOCUS_NONE) {
+ if (mAudioFocus != AudioManager.AUDIOFOCUS_GAIN) {
requestAudioFocus();
}
- // On the off change mMediaPlayer errors out and dies, we want to make sure we retry this.
- // This function immediately exits if we have a MediaPlayer object.
- requestMediaKeyFocus();
}
private synchronized int requestAudioFocus() {
@@ -277,8 +275,11 @@
}
/**
- * Creates a MediaPlayer that plays a silent audio sample so that MediaSessionService will be
- * aware of the fact that Bluetooth is playing audio.
+ * Plays a silent audio sample so that MediaSessionService will be aware of the fact that
+ * Bluetooth is playing audio.
+ *
+ * Creates a new MediaPlayer if one does not already exist. Repeat calls to this function are
+ * safe and will result in the silent audio sample again.
*
* This allows the MediaSession in AVRCP Controller to be routed media key events, if we've
* chosen to use it.
@@ -286,25 +287,25 @@
private synchronized void requestMediaKeyFocus() {
if (DBG) Log.d(TAG, "requestMediaKeyFocus()");
- if (mMediaPlayer != null) return;
-
- AudioAttributes attrs = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .build();
-
- mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
- mAudioManager.generateAudioSessionId());
if (mMediaPlayer == null) {
- Log.e(TAG, "Failed to initialize media player. You may not get media key events");
- return;
- }
+ AudioAttributes attrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
- mMediaPlayer.setLooping(false);
- mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
- Log.e(TAG, "Silent media player error: " + what + ", " + extra);
- releaseMediaKeyFocus();
- return false;
- });
+ mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs,
+ mAudioManager.generateAudioSessionId());
+ if (mMediaPlayer == null) {
+ Log.e(TAG, "Failed to initialize media player. You may not get media key events");
+ return;
+ }
+
+ mMediaPlayer.setLooping(false);
+ mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+ Log.e(TAG, "Silent media player error: " + what + ", " + extra);
+ releaseMediaKeyFocus();
+ return false;
+ });
+ }
mMediaPlayer.start();
BluetoothMediaBrowserService.setActive(true);
@@ -313,7 +314,6 @@
private synchronized void abandonAudioFocus() {
if (DBG) Log.d(TAG, "abandonAudioFocus()");
stopFluorideStreaming();
- releaseMediaKeyFocus();
mAudioManager.abandonAudioFocus(mAudioFocusListener);
mAudioFocus = AudioManager.AUDIOFOCUS_NONE;
}
@@ -336,9 +336,11 @@
private void startFluorideStreaming() {
mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);
mA2dpSinkService.informAudioTrackGainNative(1.0f);
+ requestMediaKeyFocus();
}
private void stopFluorideStreaming() {
+ releaseMediaKeyFocus();
mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_LOST);
}
diff --git a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
index 1a2bba7..fc35fc1 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpTargetService.java
@@ -114,6 +114,17 @@
Log.d(TAG, "request to disconnect device " + device);
}
}
+ } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ if (streamType == AudioManager.STREAM_MUSIC) {
+ int volume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
+ BluetoothDevice activeDevice = getA2dpActiveDevice();
+ if (activeDevice != null
+ && !mVolumeManager.getAbsoluteVolumeSupported(activeDevice)) {
+ Log.d(TAG, "stream volume change to " + volume + " " + activeDevice);
+ mVolumeManager.storeVolumeForDevice(activeDevice, volume);
+ }
+ }
}
}
}
@@ -153,7 +164,7 @@
@Override
protected boolean start() {
if (sInstance != null) {
- Log.wtfStack(TAG, "The service has already been initialized");
+ Log.wtf(TAG, "The service has already been initialized");
return false;
}
@@ -185,6 +196,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
registerReceiver(mReceiver, filter);
// Only allow the service to be used once it is initialized
@@ -219,6 +231,23 @@
private void init() {
}
+ private BluetoothDevice getA2dpActiveDevice() {
+ A2dpService service = mFactory.getA2dpService();
+ if (service == null) {
+ return null;
+ }
+ return service.getActiveDevice();
+ }
+
+ private void setA2dpActiveDevice(BluetoothDevice device) {
+ A2dpService service = A2dpService.getA2dpService();
+ if (service == null) {
+ Log.d(TAG, "setA2dpActiveDevice: A2dp service not found");
+ return;
+ }
+ service.setActiveDevice(device);
+ }
+
void deviceConnected(BluetoothDevice device, boolean absoluteVolume) {
Log.i(TAG, "deviceConnected: device=" + device + " absoluteVolume=" + absoluteVolume);
mVolumeManager.deviceConnected(device, absoluteVolume);
@@ -263,7 +292,7 @@
// TODO (apanicke): Add checks to blacklist Absolute Volume devices if they behave poorly.
void setVolume(int avrcpVolume) {
- BluetoothDevice activeDevice = mFactory.getA2dpService().getActiveDevice();
+ BluetoothDevice activeDevice = getA2dpActiveDevice();
if (activeDevice == null) {
Log.d(TAG, "setVolume: no active device");
return;
@@ -277,7 +306,7 @@
* volume.
*/
public void sendVolumeChanged(int deviceVolume) {
- BluetoothDevice activeDevice = mFactory.getA2dpService().getActiveDevice();
+ BluetoothDevice activeDevice = getA2dpActiveDevice();
if (activeDevice == null) {
Log.d(TAG, "sendVolumeChanged: no active device");
return;
@@ -343,9 +372,9 @@
void setActiveDevice(BluetoothDevice device) {
Log.i(TAG, "setActiveDevice: device=" + device);
if (device == null) {
- Log.wtfStack(TAG, "setActiveDevice: could not find device " + device);
+ Log.wtf(TAG, "setActiveDevice: could not find device " + device);
}
- A2dpService.getA2dpService().setActiveDevice(device);
+ setA2dpActiveDevice(device);
}
/**
diff --git a/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index c1369f8..e48d428 100644
--- a/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
+++ b/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -115,12 +115,11 @@
volumeMapEditor.apply();
}
- synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device) {
+ synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device, int storeVolume) {
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
return;
}
SharedPreferences.Editor pref = getVolumeMap().edit();
- int storeVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
Log.i(TAG, "storeVolume: Storing stream volume level for device " + device
+ " : " + storeVolume);
mVolumeMap.put(device, storeVolume);
@@ -130,6 +129,11 @@
pref.apply();
}
+ synchronized void storeVolumeForDevice(@NonNull BluetoothDevice device) {
+ int storeVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+ storeVolumeForDevice(device, storeVolume);
+ }
+
synchronized void removeStoredVolumeForDevice(@NonNull BluetoothDevice device) {
if (device.getBondState() != BluetoothDevice.BOND_NONE) {
return;
@@ -183,6 +187,17 @@
storeVolumeForDevice(device);
}
+ /**
+ * True if remote device supported Absolute volume, false if remote device is not supported or
+ * not connected.
+ */
+ boolean getAbsoluteVolumeSupported(BluetoothDevice device) {
+ if (mDeviceMap.containsKey(device)) {
+ return mDeviceMap.get(device);
+ }
+ return false;
+ }
+
@Override
public synchronized void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
if (mCurrentDevice == null) {
diff --git a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
index eafe3b9..e009f28 100644
--- a/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
+++ b/src/com/android/bluetooth/avrcp/BrowsablePlayerConnector.java
@@ -76,7 +76,7 @@
return sInjectConnector;
}
if (cb == null) {
- Log.wtfStack(TAG, "Null callback passed");
+ Log.wtf(TAG, "Null callback passed");
return null;
}
diff --git a/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java b/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
index 783da64..6ffdac5 100644
--- a/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/BrowsedPlayerWrapper.java
@@ -136,7 +136,7 @@
*/
boolean connect(ConnectionCallback cb) {
if (cb == null) {
- Log.wtfStack(TAG, "connect: Trying to connect to " + mPackageName
+ Log.wtf(TAG, "connect: Trying to connect to " + mPackageName
+ "with null callback");
}
return setCallbackAndConnect((int status, BrowsedPlayerWrapper wrapper) -> {
@@ -245,7 +245,7 @@
}
if (cb == null) {
- Log.wtfStack(TAG, "getFolderItems: Trying to connect to " + mPackageName
+ Log.wtf(TAG, "getFolderItems: Trying to connect to " + mPackageName
+ "with null browse callback");
}
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerList.java b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
index 6fc8138..e7cb96a 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerList.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerList.java
@@ -18,12 +18,12 @@
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.session.MediaSession;
@@ -31,6 +31,7 @@
import android.media.session.PlaybackState;
import android.os.Handler;
import android.os.Looper;
+import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -137,7 +138,8 @@
(MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
mMediaSessionManager.addOnActiveSessionsChangedListener(
mActiveSessionsChangedListener, null, new Handler(looper));
- mMediaSessionManager.setCallback(mButtonDispatchCallback, null);
+ mMediaSessionManager.addOnMediaKeyEventSessionChangedListener(
+ mContext.getMainExecutor(), mMediaKeyEventSessionChangedListener);
}
void init(AvrcpTargetService.ListCallback callback) {
@@ -200,7 +202,8 @@
mActivePlayerId = NO_ACTIVE_PLAYER;
mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveSessionsChangedListener);
- mMediaSessionManager.setCallback(null, null);
+ mMediaSessionManager.removeOnMediaKeyEventSessionChangedListener(
+ mMediaKeyEventSessionChangedListener);
mMediaSessionManager = null;
mAudioManager.unregisterAudioPlaybackCallback(mAudioPlaybackCallback);
@@ -691,10 +694,12 @@
boolean isActive = false;
Log.v(TAG, "onPlaybackConfigChanged(): Configs list size=" + configs.size());
for (AudioPlaybackConfiguration config : configs) {
- if (config.isActive()) {
+ if (config.isActive() && (config.getAudioAttributes().getUsage()
+ == AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+ && (config.getAudioAttributes().getContentType()
+ == AudioAttributes.CONTENT_TYPE_SPEECH)) {
if (DEBUG) {
- Log.d(TAG, "onPlaybackConfigChanged(): config="
- + AudioPlaybackConfiguration.toLogFriendlyString(config));
+ Log.d(TAG, "onPlaybackConfigChanged(): config=" + config);
}
isActive = true;
}
@@ -738,61 +743,45 @@
}
};
- private final MediaSessionManager.Callback mButtonDispatchCallback =
- new MediaSessionManager.Callback() {
+ private final MediaSessionManager.OnMediaKeyEventSessionChangedListener
+ mMediaKeyEventSessionChangedListener =
+ new MediaSessionManager.OnMediaKeyEventSessionChangedListener() {
@Override
- public void onMediaKeyEventDispatched(KeyEvent event, MediaSession.Token token) {
- // TODO (apanicke): Add logging for these
- }
-
- @Override
- public void onMediaKeyEventDispatched(KeyEvent event, ComponentName receiver) {
- // TODO (apanicke): Add logging for these
- }
-
- @Override
- public void onAddressedPlayerChanged(MediaSession.Token token) {
- android.media.session.MediaController controller =
- new android.media.session.MediaController(mContext, token);
-
+ public void onMediaKeyEventSessionChanged(String packageName,
+ MediaSession.Token token) {
if (mMediaSessionManager == null) {
- Log.w(TAG, "onAddressedPlayerChanged(Token): Unexpected callback "
- + "from the MediaSessionManager");
+ Log.w(TAG, "onMediaKeyEventSessionChanged(): Unexpected callback "
+ + "from the MediaSessionManager, pkg" + packageName + ", token="
+ + token);
return;
}
-
- if (!mMediaPlayerIds.containsKey(controller.getPackageName())) {
- // Since we have a controller, we can try to to recover by adding the
- // player and then setting it as active.
- Log.w(TAG, "onAddressedPlayerChanged(Token): Addressed Player "
- + "changed to a player we didn't have a session for");
- addMediaPlayer(controller);
- }
-
- Log.i(TAG, "onAddressedPlayerChanged: token=" + controller.getPackageName());
- setActivePlayer(mMediaPlayerIds.get(controller.getPackageName()));
- }
-
- @Override
- public void onAddressedPlayerChanged(ComponentName receiver) {
- if (mMediaSessionManager == null) {
- Log.w(TAG, "onAddressedPlayerChanged(Component): Unexpected callback "
- + "from the MediaSessionManager");
+ if (TextUtils.isEmpty(packageName)) {
return;
}
+ if (token != null) {
+ android.media.session.MediaController controller =
+ new android.media.session.MediaController(mContext, token);
+ if (!mMediaPlayerIds.containsKey(controller.getPackageName())) {
+ // Since we have a controller, we can try to to recover by adding the
+ // player and then setting it as active.
+ Log.w(TAG, "onMediaKeyEventSessionChanged(Token): Addressed Player "
+ + "changed to a player we didn't have a session for");
+ addMediaPlayer(controller);
+ }
- if (receiver == null) {
- return;
+ Log.i(TAG, "onMediaKeyEventSessionChanged: token="
+ + controller.getPackageName());
+ setActivePlayer(mMediaPlayerIds.get(controller.getPackageName()));
+ } else {
+ if (!mMediaPlayerIds.containsKey(packageName)) {
+ e("onMediaKeyEventSessionChanged(PackageName): Media key event session "
+ + "changed to a player we don't have a session for");
+ return;
+ }
+
+ Log.i(TAG, "onMediaKeyEventSessionChanged: packageName=" + packageName);
+ setActivePlayer(mMediaPlayerIds.get(packageName));
}
-
- if (!mMediaPlayerIds.containsKey(receiver.getPackageName())) {
- e("onAddressedPlayerChanged(Component): Addressed Player "
- + "changed to a player we don't have a session for");
- return;
- }
-
- Log.i(TAG, "onAddressedPlayerChanged: component=" + receiver.getPackageName());
- setActivePlayer(mMediaPlayerIds.get(receiver.getPackageName()));
}
};
@@ -821,7 +810,7 @@
private static void e(String message) {
if (sTesting) {
- Log.wtfStack(TAG, message);
+ Log.wtf(TAG, message);
} else {
Log.e(TAG, message);
}
diff --git a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
index 230d876..38a270f 100644
--- a/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
+++ b/src/com/android/bluetooth/avrcp/MediaPlayerWrapper.java
@@ -312,7 +312,7 @@
// TODO(apanicke): Add metric collection here.
- if (sTesting) Log.wtfStack(TAG, "Crashing the stack");
+ if (sTesting) Log.wtf(TAG, "Crashing the stack");
}
}
@@ -486,7 +486,7 @@
private static void e(String message) {
if (sTesting) {
- Log.wtfStack(TAG, message);
+ Log.wtf(TAG, message);
} else {
Log.e(TAG, message);
}
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java b/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
index d6e846d..7bf38bd 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaBrowser.java
@@ -160,6 +160,6 @@
public void testInit(Context context, ComponentName serviceComponent,
ConnectionCallback callback, Bundle rootHints) {
// This is only used by Mockito to capture the constructor arguments on creation
- Log.wtfStack("AvrcpMockMediaBrowser", "This function should never be called");
+ Log.wtf("AvrcpMockMediaBrowser", "This function should never be called");
}
}
diff --git a/src/com/android/bluetooth/avrcp/mockable/MediaController.java b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
index 5c7b73f..0d160bc 100644
--- a/src/com/android/bluetooth/avrcp/mockable/MediaController.java
+++ b/src/com/android/bluetooth/avrcp/mockable/MediaController.java
@@ -215,14 +215,14 @@
* Wrapper for MediaController.controlsSameSession(MediaController other)
*/
public boolean controlsSameSession(MediaController other) {
- return mDelegate.controlsSameSession(other.getWrappedInstance());
+ return mDelegate.getSessionToken().equals(other.getWrappedInstance().getSessionToken());
}
/**
* Wrapper for MediaController.controlsSameSession(MediaController other)
*/
public boolean controlsSameSession(android.media.session.MediaController other) {
- return mDelegate.controlsSameSession(other);
+ return mDelegate.getSessionToken().equals(other.getSessionToken());
}
/**
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
old mode 100644
new mode 100755
index 693b4a2..f46820c
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java
@@ -172,9 +172,11 @@
}
}
+ // If we don't find a node in the tree then do not have any way to browse for the contents.
+ // Return an empty list instead.
if (requestedNode == null) {
if (DBG) Log.d(TAG, "Didn't find a node");
- return null;
+ return new ArrayList(0);
} else {
if (!requestedNode.isCached()) {
if (DBG) Log.d(TAG, "node is not cached");
@@ -435,6 +437,18 @@
}
}
+ private void onAvailablePlayerChanged(byte[] address) {
+ if (DBG) {
+ Log.d(TAG," onAvailablePlayerChanged");
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
+
+ AvrcpControllerStateMachine stateMachine = getStateMachine(device);
+ if (stateMachine != null) {
+ stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED);
+ }
+ }
+
// Browsing related JNI callbacks.
void handleGetFolderItemsRsp(byte[] address, int status, MediaItem[] items) {
if (DBG) {
diff --git a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
old mode 100644
new mode 100755
index e3abc4d..95867a9
--- a/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
+++ b/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
@@ -37,8 +37,8 @@
import com.android.bluetooth.a2dpsink.A2dpSinkService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
import java.util.ArrayList;
import java.util.List;
@@ -79,6 +79,7 @@
static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217;
static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218;
+ static final int MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED = 219;
//300->399 Events for Browsing
static final int MESSAGE_GET_FOLDER_ITEMS = 300;
@@ -296,9 +297,9 @@
@Override
public void enter() {
if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
+ BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
- BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
} else {
logD("ReEnteringConnected");
}
@@ -400,6 +401,10 @@
BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
return true;
+ case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED:
+ processAvailablePlayerChanged();
+ return true;
+
case DISCONNECT:
transitionTo(mDisconnecting);
return true;
@@ -471,6 +476,13 @@
PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)});
}
+
+ private void processAvailablePlayerChanged() {
+ logD("processAvailablePlayerChanged");
+ mBrowseTree.mRootNode.setCached(false);
+ mBrowseTree.mRootNode.setExpectedChildren(255);
+ BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
+ }
}
// Handle the get folder listing action
@@ -881,7 +893,6 @@
private boolean shouldRequestFocus() {
return mService.getResources()
- .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus)
- || !mAudioManager.isMusicActive();
+ .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
}
}
diff --git a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
index a0b1224..df88b80 100644
--- a/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
+++ b/src/com/android/bluetooth/avrcpcontroller/BluetoothMediaBrowserService.java
@@ -63,6 +63,16 @@
// Browsing related structures.
private List<MediaSessionCompat.QueueItem> mMediaQueue = new ArrayList<>();
+ // Media Framework Content Style constants
+ private static final String CONTENT_STYLE_SUPPORTED =
+ "android.media.browse.CONTENT_STYLE_SUPPORTED";
+ public static final String CONTENT_STYLE_PLAYABLE_HINT =
+ "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT";
+ public static final String CONTENT_STYLE_BROWSABLE_HINT =
+ "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT";
+ public static final int CONTENT_STYLE_LIST_ITEM_HINT_VALUE = 1;
+ public static final int CONTENT_STYLE_GRID_ITEM_HINT_VALUE = 2;
+
// Error messaging extras
public static final String ERROR_RESOLUTION_ACTION_INTENT =
"android.media.extras.ERROR_RESOLUTION_ACTION_INTENT";
@@ -117,6 +127,14 @@
mSession.setPlaybackState(errorState);
}
+ private Bundle getDefaultStyle() {
+ Bundle style = new Bundle();
+ style.putBoolean(CONTENT_STYLE_SUPPORTED, true);
+ style.putInt(CONTENT_STYLE_BROWSABLE_HINT, CONTENT_STYLE_GRID_ITEM_HINT_VALUE);
+ style.putInt(CONTENT_STYLE_PLAYABLE_HINT, CONTENT_STYLE_LIST_ITEM_HINT_VALUE);
+ return style;
+ }
+
@Override
public synchronized void onLoadChildren(final String parentMediaId,
final Result<List<MediaBrowserCompat.MediaItem>> result) {
@@ -133,7 +151,8 @@
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
if (DBG) Log.d(TAG, "onGetRoot");
- return new BrowserRoot(BrowseTree.ROOT, null);
+ Bundle style = getDefaultStyle();
+ return new BrowserRoot(BrowseTree.ROOT, style);
}
private void updateNowPlayingQueue(BrowseTree.BrowseNode node) {
diff --git a/src/com/android/bluetooth/btservice/AdapterProperties.java b/src/com/android/bluetooth/btservice/AdapterProperties.java
index 4b4b4df..8418e0e 100644
--- a/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -43,10 +43,10 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
-import android.util.StatsLog;
import androidx.annotation.VisibleForTesting;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
@@ -580,8 +580,8 @@
Log.d(TAG,
"PROFILE_CONNECTION_STATE_CHANGE: profile=" + profile + ", device=" + device + ", "
+ prevState + " -> " + state);
- StatsLog.write(StatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state, 0 /* deprecated */,
- profile, mService.obfuscateAddress(device));
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state,
+ 0 /* deprecated */, profile, mService.obfuscateAddress(device));
if (!isNormalStateTransition(prevState, state)) {
Log.w(TAG,
@@ -916,15 +916,6 @@
setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
}
- void onBluetoothDisable() {
- // From STATE_ON to BLE_ON
- debugLog("onBluetoothDisable()");
- // Turn off any Device Search/Inquiry
- mService.cancelDiscovery();
- // Set the scan_mode to NONE (no incoming connections).
- setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
- }
-
void discoveryStateChangeCallback(int state) {
infoLog("Callback:discoveryStateChangeCallback with state:" + state);
synchronized (mObject) {
diff --git a/src/com/android/bluetooth/btservice/AdapterService.java b/src/com/android/bluetooth/btservice/AdapterService.java
index e4a5ae0..09f2166 100644
--- a/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/src/com/android/bluetooth/btservice/AdapterService.java
@@ -16,6 +16,16 @@
package com.android.bluetooth.btservice;
+import static com.android.bluetooth.Utils.addressToBytes;
+import static com.android.bluetooth.Utils.callerIsSystemOrActiveOrManagedUser;
+import static com.android.bluetooth.Utils.callerIsSystemOrActiveUser;
+import static com.android.bluetooth.Utils.enforceBluetoothAdminPermission;
+import static com.android.bluetooth.Utils.enforceBluetoothPermission;
+import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
+import static com.android.bluetooth.Utils.enforceDumpPermission;
+import static com.android.bluetooth.Utils.enforceLocalMacAddressPermission;
+
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AppOpsManager;
@@ -23,6 +33,7 @@
import android.app.Service;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAdapter.ActiveDeviceUse;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -50,7 +61,6 @@
import android.os.Message;
import android.os.ParcelUuid;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -63,11 +73,10 @@
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
-import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
@@ -90,6 +99,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.ArrayUtils;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -100,6 +110,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
public class AdapterService extends Service {
private static final String TAG = "BluetoothAdapterService";
@@ -205,7 +216,6 @@
private String mWakeLockName;
private UserManager mUserManager;
- private ProfileObserver mProfileObserver;
private PhonePolicy mPhonePolicy;
private ActiveDeviceManager mActiveDeviceManager;
private DatabaseManager mDatabaseManager;
@@ -440,8 +450,6 @@
mSdpManager = SdpManager.init(this);
registerReceiver(mAlarmBroadcastReceiver, new IntentFilter(ACTION_ALARM_WAKEUP));
- mProfileObserver = new ProfileObserver(getApplicationContext(), this, new Handler());
- mProfileObserver.start();
// Phone policy is specific to phone implementations and hence if a device wants to exclude
// it out then it can be disabled by using the flag below.
@@ -483,9 +491,9 @@
}.execute();
try {
- int systemUiUid = getApplicationContext().getPackageManager().getPackageUidAsUser(
- "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
- UserHandle.USER_SYSTEM);
+ int systemUiUid = getApplicationContext().getPackageManager().getPackageUid(
+ "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY);
+
Utils.setSystemUiUid(systemUiUid);
} catch (PackageManager.NameNotFoundException e) {
// Some platforms, such as wearables do not have a system ui.
@@ -493,8 +501,7 @@
}
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- getApplicationContext().registerReceiverAsUser(sUserSwitchedReceiver, UserHandle.ALL,
- filter, null, null);
+ getApplicationContext().registerReceiverForAllUsers(sUserSwitchedReceiver, filter, null, null);
int fuid = ActivityManager.getCurrentUser();
Utils.setForegroundUserId(fuid);
}
@@ -515,7 +522,6 @@
@Override
public void onDestroy() {
debugLog("onDestroy()");
- mProfileObserver.stop();
if (!isMock()) {
// TODO(b/27859763)
Log.i(TAG, "Force exit to cleanup internal state in Bluetooth stack");
@@ -563,8 +569,8 @@
} catch (RemoteException e) {
Log.w(TAG, "RemoteException trying to send a reset to BatteryStats");
}
- StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, -1, null,
- StatsLog.BLE_SCAN_STATE_CHANGED__STATE__RESET, false, false, false);
+ BluetoothStatsLog.write_non_chained(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, -1, null,
+ BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__RESET, false, false, false);
//Start Gatt service
setProfileServiceState(GattService.class, BluetoothAdapter.STATE_ON);
@@ -600,17 +606,6 @@
getContentResolver(), Settings.Global.BLUETOOTH_CLASS_OF_DEVICE, 0);
}
- private boolean storeBluetoothClassConfig(int bluetoothClass) {
- boolean result = Settings.Global.putInt(
- getContentResolver(), Settings.Global.BLUETOOTH_CLASS_OF_DEVICE, bluetoothClass);
-
- if (!result) {
- Log.e(TAG, "Error storing BluetoothClass config - " + bluetoothClass);
- }
-
- return result;
- }
-
void startProfileServices() {
debugLog("startCoreServices()");
Class[] supportedProfileServices = Config.getSupportedProfiles();
@@ -626,7 +621,10 @@
}
void stopProfileServices() {
- mAdapterProperties.onBluetoothDisable();
+ // Make sure to stop classic background tasks now
+ cancelDiscoveryNative();
+ mAdapterProperties.setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
+
Class[] supportedProfileServices = Config.getSupportedProfiles();
if (supportedProfileServices.length == 1 && (mRunningProfiles.size() == 1
&& GattService.class.getSimpleName().equals(mRunningProfiles.get(0).getName()))) {
@@ -815,36 +813,36 @@
}
if (profile == BluetoothProfile.HEADSET) {
- return (BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.HSP_AG)
- && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.HSP))
- || (BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.Handsfree_AG)
- && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Handsfree));
+ return (ArrayUtils.contains(localDeviceUuids, BluetoothUuid.HSP_AG)
+ && ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HSP))
+ || (ArrayUtils.contains(localDeviceUuids, BluetoothUuid.HFP_AG)
+ && ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HFP));
}
if (profile == BluetoothProfile.HEADSET_CLIENT) {
- return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Handsfree_AG)
- && BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.Handsfree);
+ return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HFP_AG)
+ && ArrayUtils.contains(localDeviceUuids, BluetoothUuid.HFP);
}
if (profile == BluetoothProfile.A2DP) {
- return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AdvAudioDist)
- || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AudioSink);
+ return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.ADV_AUDIO_DIST)
+ || ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.A2DP_SINK);
}
if (profile == BluetoothProfile.A2DP_SINK) {
- return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AdvAudioDist)
- || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.AudioSource);
+ return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.ADV_AUDIO_DIST)
+ || ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.A2DP_SOURCE);
}
if (profile == BluetoothProfile.OPP) {
- return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.ObexObjectPush);
+ return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.OBEX_OBJECT_PUSH);
}
if (profile == BluetoothProfile.HID_HOST) {
- return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Hid)
- || BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.Hogp);
+ return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HID)
+ || ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HOGP);
}
if (profile == BluetoothProfile.HID_DEVICE) {
return mHidDeviceService.getConnectionState(device)
== BluetoothProfile.STATE_DISCONNECTED;
}
if (profile == BluetoothProfile.PAN) {
- return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.NAP);
+ return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.NAP);
}
if (profile == BluetoothProfile.MAP) {
return mMapService.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
@@ -856,14 +854,14 @@
return true;
}
if (profile == BluetoothProfile.PBAP_CLIENT) {
- return BluetoothUuid.isUuidPresent(localDeviceUuids, BluetoothUuid.PBAP_PCE)
- && BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.PBAP_PSE);
+ return ArrayUtils.contains(localDeviceUuids, BluetoothUuid.PBAP_PCE)
+ && ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.PBAP_PSE);
}
if (profile == BluetoothProfile.HEARING_AID) {
- return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.HearingAid);
+ return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HEARING_AID);
}
if (profile == BluetoothProfile.SAP) {
- return BluetoothUuid.isUuidPresent(remoteDeviceUuids, BluetoothUuid.SAP);
+ return ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.SAP);
}
Log.e(TAG, "isSupported: Unexpected profile passed in to function: " + profile);
@@ -878,40 +876,40 @@
*/
private boolean isAnyProfileEnabled(BluetoothDevice device) {
- if (mA2dpService != null
- && mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mA2dpService != null && mA2dpService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
- if (mA2dpSinkService != null
- && mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mA2dpSinkService != null && mA2dpSinkService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
- if (mHeadsetService != null
- && mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mHeadsetService != null && mHeadsetService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
- if (mHeadsetClientService != null
- && mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mHeadsetClientService != null && mHeadsetClientService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
- if (mMapClientService != null
- && mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mMapClientService != null && mMapClientService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
- if (mHidHostService != null
- && mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mHidHostService != null && mHidHostService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
- if (mPanService != null
- && mPanService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mPanService != null && mPanService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
- if (mPbapClientService != null
- && mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mPbapClientService != null && mPbapClientService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
- if (mHearingAidService != null
- && mHearingAidService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ if (mHearingAidService != null && mHearingAidService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return true;
}
@@ -919,66 +917,71 @@
}
/**
- * Connects only available profiles (those with {@link BluetoothProfile#PRIORITY_ON})
+ * Connects only available profiles
+ * (those with {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED})
*
* @param device is the device with which we are connecting the profiles
* @return true
*/
private boolean connectEnabledProfiles(BluetoothDevice device) {
ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
- ParcelUuid[] localDeviceUuids = getUuids();
+ ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
- BluetoothProfile.A2DP, device)
- && mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ BluetoothProfile.A2DP, device) && mA2dpService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting A2dp");
mA2dpService.connect(device);
}
if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
- BluetoothProfile.A2DP_SINK, device)
- && mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ BluetoothProfile.A2DP_SINK, device) && mA2dpSinkService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting A2dp Sink");
mA2dpSinkService.connect(device);
}
if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
- BluetoothProfile.HEADSET, device)
- && mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ BluetoothProfile.HEADSET, device) && mHeadsetService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting Headset Profile");
mHeadsetService.connect(device);
}
if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HEADSET_CLIENT, device)
- && mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ && mHeadsetClientService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting HFP");
mHeadsetClientService.connect(device);
}
if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.MAP_CLIENT, device)
- && mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ && mMapClientService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting MAP");
mMapClientService.connect(device);
}
if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
- BluetoothProfile.HID_HOST, device)
- && mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ BluetoothProfile.HID_HOST, device) && mHidHostService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting Hid Host Profile");
mHidHostService.connect(device);
}
if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
- BluetoothProfile.PAN, device)
- && mPanService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ BluetoothProfile.PAN, device) && mPanService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting Pan Profile");
mPanService.connect(device);
}
if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.PBAP_CLIENT, device)
- && mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ && mPbapClientService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting Pbap");
mPbapClientService.connect(device);
}
if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HEARING_AID, device)
- && mHearingAidService.getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
+ && mHearingAidService.getConnectionPolicy(device)
+ > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
Log.i(TAG, "connectEnabledProfiles: Connecting Hearing Aid Profile");
mHearingAidService.connect(device);
}
@@ -1041,7 +1044,8 @@
* why an inner instance class should be avoided.
*
*/
- private static class AdapterServiceBinder extends IBluetooth.Stub {
+ @VisibleForTesting
+ public static class AdapterServiceBinder extends IBluetooth.Stub {
private AdapterService mService;
AdapterServiceBinder(AdapterService svc) {
@@ -1060,307 +1064,287 @@
}
@Override
- public boolean isEnabled() {
- // don't check caller, may be called from system UI
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.isEnabled();
- }
-
- @Override
public int getState() {
// don't check caller, may be called from system UI
AdapterService service = getService();
if (service == null) {
return BluetoothAdapter.STATE_OFF;
}
+
+ enforceBluetoothPermission(service);
+
return service.getState();
}
@Override
- public boolean enable() {
- if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
- Log.w(TAG, "enable() - Not allowed for non-active user and non system user");
- return false;
- }
+ public boolean enable(boolean quietMode) {
AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.enable();
- }
-
- @Override
- public boolean enableNoAutoConnect() {
- if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
- Log.w(TAG, "enableNoAuto() - Not allowed for non-active user and non system user");
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "enable")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.enableNoAutoConnect();
+ enforceBluetoothAdminPermission(service);
+
+ return service.enable(quietMode);
}
@Override
public boolean disable() {
- if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
- Log.w(TAG, "disable() - Not allowed for non-active user and non system user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "disable")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
+ enforceBluetoothAdminPermission(service);
+
return service.disable();
}
@Override
public String getAddress() {
- if ((Binder.getCallingUid() != Process.SYSTEM_UID)
- && (!Utils.checkCallerAllowManagedProfiles(mService))) {
- Log.w(TAG, "getAddress() - Not allowed for non-active user and non system user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAddress")) {
return null;
}
- AdapterService service = getService();
- if (service == null) {
- return null;
- }
- return service.getAddress();
+ enforceBluetoothPermission(service);
+ enforceLocalMacAddressPermission(service);
+
+ return Utils.getAddressStringFromByte(service.mAdapterProperties.getAddress());
}
@Override
public ParcelUuid[] getUuids() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getUuids() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getUuids")) {
return new ParcelUuid[0];
}
- AdapterService service = getService();
- if (service == null) {
- return new ParcelUuid[0];
- }
- return service.getUuids();
+ enforceBluetoothPermission(service);
+
+ return service.mAdapterProperties.getUuids();
}
@Override
public String getName() {
- if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!Utils.checkCaller())) {
- Log.w(TAG, "getName() - Not allowed for non-active user and non system user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getName")) {
return null;
}
- AdapterService service = getService();
- if (service == null) {
- return null;
- }
+ enforceBluetoothPermission(service);
+
return service.getName();
}
@Override
public boolean setName(String name) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setName() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setName")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.setName(name);
+ enforceBluetoothAdminPermission(service);
+
+ return service.mAdapterProperties.setName(name);
}
@Override
public BluetoothClass getBluetoothClass() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getBluetoothClass() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getBluetoothClass")) {
return null;
}
- AdapterService service = getService();
- if (service == null) return null;
- return service.getBluetoothClass();
+ enforceBluetoothAdminPermission(service);
+
+ return service.mAdapterProperties.getBluetoothClass();
}
@Override
public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setBluetoothClass")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
+ enforceBluetoothPrivilegedPermission(service);
+
+ if (!service.mAdapterProperties.setBluetoothClass(bluetoothClass)) {
+ return false;
}
- return service.setBluetoothClass(bluetoothClass);
+
+ return Settings.Global.putInt(
+ service.getContentResolver(),
+ Settings.Global.BLUETOOTH_CLASS_OF_DEVICE,
+ bluetoothClass.getClassOfDevice());
}
@Override
public int getIoCapability() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getIoCapability")) {
return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
}
- AdapterService service = getService();
- if (service == null) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
- return service.getIoCapability();
+ enforceBluetoothAdminPermission(service);
+
+ return service.mAdapterProperties.getIoCapability();
}
@Override
public boolean setIoCapability(int capability) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setIoCapability")) {
return false;
}
- AdapterService service = getService();
- if (service == null) return false;
- return service.setIoCapability(capability);
+ enforceBluetoothPrivilegedPermission(service);
+
+ if (!isValidIoCapability(capability)) {
+ return false;
+ }
+
+ return service.mAdapterProperties.setIoCapability(capability);
}
@Override
public int getLeIoCapability() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getLeIoCapability")) {
return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
}
- AdapterService service = getService();
- if (service == null) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
- return service.getLeIoCapability();
+ enforceBluetoothAdminPermission(service);
+
+ return service.mAdapterProperties.getLeIoCapability();
}
@Override
public boolean setLeIoCapability(int capability) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setBluetoothClass() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setLeIoCapability")) {
return false;
}
- AdapterService service = getService();
- if (service == null) return false;
- return service.setLeIoCapability(capability);
+ enforceBluetoothPrivilegedPermission(service);
+
+ if (!isValidIoCapability(capability)) {
+ return false;
+ }
+
+ return service.mAdapterProperties.setLeIoCapability(capability);
}
@Override
public int getScanMode() {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "getScanMode() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getScanMode")) {
return BluetoothAdapter.SCAN_MODE_NONE;
}
- AdapterService service = getService();
- if (service == null) {
- return BluetoothAdapter.SCAN_MODE_NONE;
- }
- return service.getScanMode();
+ enforceBluetoothPermission(service);
+
+ return service.mAdapterProperties.getScanMode();
}
@Override
public boolean setScanMode(int mode, int duration) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setScanMode() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setScanMode")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.setScanMode(mode, duration);
+ enforceBluetoothPrivilegedPermission(service);
+
+ service.mAdapterProperties.setDiscoverableTimeout(duration);
+ return service.mAdapterProperties.setScanMode(convertScanModeToHal(mode));
}
@Override
public int getDiscoverableTimeout() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getDiscoverableTimeout() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getDiscoverableTimeout")) {
return 0;
}
- AdapterService service = getService();
- if (service == null) {
- return 0;
- }
- return service.getDiscoverableTimeout();
+ enforceBluetoothPermission(service);
+
+ return service.mAdapterProperties.getDiscoverableTimeout();
}
@Override
public boolean setDiscoverableTimeout(int timeout) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setDiscoverableTimeout() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setDiscoverableTimeout")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.setDiscoverableTimeout(timeout);
+ enforceBluetoothPermission(service);
+
+ return service.mAdapterProperties.setDiscoverableTimeout(timeout);
}
@Override
- public boolean startDiscovery(String callingPackage) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "startDiscovery() - Not allowed for non-active user");
+ public boolean startDiscovery(String callingPackage, String callingFeatureId) {
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "startDiscovery")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.startDiscovery(callingPackage);
+ enforceBluetoothAdminPermission(service);
+
+ return service.startDiscovery(callingPackage, callingFeatureId);
}
@Override
public boolean cancelDiscovery() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "cancelDiscovery() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelDiscovery")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.cancelDiscovery();
+ enforceBluetoothAdminPermission(service);
+
+ service.debugLog("cancelDiscovery");
+ return service.cancelDiscoveryNative();
}
@Override
public boolean isDiscovering() {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "isDiscovering() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "isDiscovering")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.isDiscovering();
+ enforceBluetoothPermission(service);
+
+ return service.mAdapterProperties.isDiscovering();
}
@Override
public long getDiscoveryEndMillis() {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getDiscoveryEndMillis() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getDiscoveryEndMillis")) {
return -1;
}
+ enforceBluetoothPermission(service);
+
+ return service.mAdapterProperties.discoveryEndMillis();
+ }
+
+ @Override
+ public List<BluetoothDevice> getMostRecentlyConnectedDevices() {
+ // don't check caller, may be called from system UI
AdapterService service = getService();
if (service == null) {
- return -1;
+ return new ArrayList<>();
}
- return service.getDiscoveryEndMillis();
+
+ enforceBluetoothAdminPermission(service);
+
+ return service.mDatabaseManager.getMostRecentlyConnectedDevices();
}
@Override
@@ -1370,6 +1354,9 @@
if (service == null) {
return new BluetoothDevice[0];
}
+
+ enforceBluetoothPermission(service);
+
return service.getBondedDevices();
}
@@ -1380,77 +1367,72 @@
if (service == null) {
return BluetoothAdapter.STATE_DISCONNECTED;
}
- return service.getAdapterConnectionState();
+
+ enforceBluetoothPermission(service);
+
+ return service.mAdapterProperties.getConnectionState();
}
@Override
public int getProfileConnectionState(int profile) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "getProfileConnectionState- Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getProfileConnectionState")) {
return BluetoothProfile.STATE_DISCONNECTED;
}
- AdapterService service = getService();
- if (service == null) {
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- return service.getProfileConnectionState(profile);
+ enforceBluetoothPermission(service);
+
+ return service.mAdapterProperties.getProfileConnectionState(profile);
}
@Override
- public boolean createBond(BluetoothDevice device, int transport) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "createBond() - Not allowed for non-active user");
- return false;
- }
-
+ public boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.createBond(device, transport, null);
- }
-
- @Override
- public boolean createBondOutOfBand(BluetoothDevice device, int transport, OobData oobData) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "createBondOutOfBand() - Not allowed for non-active user");
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "createBond")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
+ enforceBluetoothAdminPermission(service);
+
return service.createBond(device, transport, oobData);
}
@Override
public boolean cancelBondProcess(BluetoothDevice device) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "cancelBondProcess() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "cancelBondProcess")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
+ enforceBluetoothAdminPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp != null) {
+ deviceProp.setBondingInitiatedLocally(false);
}
- return service.cancelBondProcess(device);
+
+ return service.cancelBondNative(addressToBytes(device.getAddress()));
}
@Override
public boolean removeBond(BluetoothDevice device) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "removeBond() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "removeBond")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
+ enforceBluetoothAdminPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDED) {
return false;
}
- return service.removeBond(device);
+ deviceProp.setBondingInitiatedLocally(false);
+
+ Message msg = service.mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND);
+ msg.obj = device;
+ service.mBondStateMachine.sendMessage(msg);
+ return true;
}
@Override
@@ -1460,6 +1442,9 @@
if (service == null) {
return BluetoothDevice.BOND_NONE;
}
+
+ enforceBluetoothPermission(service);
+
return service.getBondState(device);
}
@@ -1470,7 +1455,11 @@
if (service == null) {
return false;
}
- return service.isBondingInitiatedLocally(device);
+
+ enforceBluetoothPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ return deviceProp != null && deviceProp.isBondingInitiatedLocally();
}
@Override
@@ -1479,7 +1468,7 @@
if (service == null) {
return 0;
}
- return service.getSupportedProfiles();
+ return Config.getSupportedProfilesBitMask();
}
@Override
@@ -1488,13 +1477,16 @@
if (service == null) {
return 0;
}
+
+ enforceBluetoothPermission(service);
+
return service.getConnectionState(device);
}
@Override
- public boolean connectAllEnabledProfiles(BluetoothDevice device) {
+ public boolean removeActiveDevice(@ActiveDeviceUse int profiles) {
if (!Utils.checkCaller()) {
- Log.w(TAG, "connectAllEnabledProfiles() - Not allowed for non-active user");
+ Log.w(TAG, "removeActiveDevice() - Not allowed for non-active user");
return false;
}
@@ -1502,284 +1494,308 @@
if (service == null) {
return false;
}
+ return service.setActiveDevice(null, profiles);
+ }
+
+ @Override
+ public boolean setActiveDevice(BluetoothDevice device, @ActiveDeviceUse int profiles) {
+ if (!Utils.checkCaller()) {
+ Log.w(TAG, "setActiveDevice() - Not allowed for non-active user");
+ return false;
+ }
+
+ AdapterService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.setActiveDevice(device, profiles);
+ }
+
+ @Override
+ public boolean connectAllEnabledProfiles(BluetoothDevice device) {
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "connectAllEnabledProfiles")) {
+ return false;
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+
return service.connectAllEnabledProfiles(device);
}
@Override
public boolean disconnectAllEnabledProfiles(BluetoothDevice device) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "disconnectAllEnabledProfiles() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null | !callerIsSystemOrActiveUser(TAG, "disconnectAllEnabledProfiles")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
+ enforceBluetoothPrivilegedPermission(service);
+
return service.disconnectAllEnabledProfiles(device);
}
@Override
public String getRemoteName(BluetoothDevice device) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "getRemoteName() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteName")) {
return null;
}
- AdapterService service = getService();
- if (service == null) {
- return null;
- }
+ enforceBluetoothPermission(service);
+
return service.getRemoteName(device);
}
@Override
public int getRemoteType(BluetoothDevice device) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "getRemoteType() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteType")) {
return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
}
- AdapterService service = getService();
- if (service == null) {
- return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
- }
- return service.getRemoteType(device);
+ enforceBluetoothPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ return deviceProp != null ? deviceProp.getDeviceType() : BluetoothDevice.DEVICE_TYPE_UNKNOWN;
}
@Override
public String getRemoteAlias(BluetoothDevice device) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "getRemoteAlias() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteAlias")) {
return null;
}
- AdapterService service = getService();
- if (service == null) {
- return null;
- }
- return service.getRemoteAlias(device);
+ enforceBluetoothPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ return deviceProp != null ? deviceProp.getAlias() : null;
}
@Override
public boolean setRemoteAlias(BluetoothDevice device, String name) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setRemoteAlias() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setRemoteAlias")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
+ enforceBluetoothPrivilegedPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) {
return false;
}
- return service.setRemoteAlias(device, name);
+ deviceProp.setAlias(device, name);
+ return true;
}
@Override
public int getRemoteClass(BluetoothDevice device) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "getRemoteClass() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteClass")) {
return 0;
}
- AdapterService service = getService();
- if (service == null) {
- return 0;
- }
- return service.getRemoteClass(device);
+ enforceBluetoothPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ return deviceProp != null ? deviceProp.getBluetoothClass() : 0;
}
@Override
public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "getRemoteUuids() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getRemoteUuids")) {
return new ParcelUuid[0];
}
- AdapterService service = getService();
- if (service == null) {
- return new ParcelUuid[0];
- }
+ enforceBluetoothPermission(service);
+
return service.getRemoteUuids(device);
}
@Override
public boolean fetchRemoteUuids(BluetoothDevice device) {
- if (!Utils.checkCallerAllowManagedProfiles(mService)) {
- Log.w(TAG, "fetchRemoteUuids() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "fetchRemoteUuids")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.fetchRemoteUuids(device);
+ enforceBluetoothPermission(service);
+
+ service.mRemoteDevices.fetchUuids(device);
+ return true;
}
@Override
public boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setPin() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setPin")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
+ enforceBluetoothAdminPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ // Only allow setting a pin in bonding state, or bonded state in case of security upgrade.
+ if (deviceProp == null || !deviceProp.isBondingOrBonded()) {
return false;
}
- return service.setPin(device, accept, len, pinCode);
+ if (pinCode.length != len) {
+ android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+ "PIN code length mismatch");
+ return false;
+ }
+ service.logUserBondResponse(device, accept, BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REPLIED);
+ return service.pinReplyNative(addressToBytes(device.getAddress()), accept, len, pinCode);
}
@Override
public boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setPasskey() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setPasskey")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
+ enforceBluetoothPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null || !deviceProp.isBonding()) {
return false;
}
- return service.setPasskey(device, accept, len, passkey);
+ if (passkey.length != len) {
+ android.util.EventLog.writeEvent(0x534e4554, "139287605", -1,
+ "Passkey length mismatch");
+ return false;
+ }
+ service.logUserBondResponse(device, accept, BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED);
+ return service.sspReplyNative(
+ addressToBytes(device.getAddress()),
+ AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY,
+ accept,
+ Utils.byteArrayToInt(passkey));
}
@Override
public boolean setPairingConfirmation(BluetoothDevice device, boolean accept) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setPairingConfirmation() - Not allowed for non-active user");
- return false;
- }
-
AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.setPairingConfirmation(device, accept);
- }
-
- @Override
- public int getPhonebookAccessPermission(BluetoothDevice device) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getPhonebookAccessPermission() - Not allowed for non-active user");
- return BluetoothDevice.ACCESS_UNKNOWN;
- }
-
- AdapterService service = getService();
- if (service == null) {
- return BluetoothDevice.ACCESS_UNKNOWN;
- }
- return service.getPhonebookAccessPermission(device);
- }
-
- @Override
- public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setSilenceMode() - Not allowed for non-active user");
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setPairingConfirmation")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
+ enforceBluetoothPrivilegedPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null || !deviceProp.isBonding()) {
return false;
}
- return service.setSilenceMode(device, silence);
+ service.logUserBondResponse(device, accept, BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED);
+ return service.sspReplyNative(
+ addressToBytes(device.getAddress()),
+ AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION,
+ accept,
+ 0);
}
@Override
public boolean getSilenceMode(BluetoothDevice device) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getSilenceMode() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getSilenceMode")) {
return false;
}
+ enforceBluetoothPrivilegedPermission(service);
+
+ return service.mSilenceDeviceManager.getSilenceMode(device);
+ }
+
+
+ @Override
+ public boolean setSilenceMode(BluetoothDevice device, boolean silence) {
AdapterService service = getService();
- if (service == null) {
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setSilenceMode")) {
return false;
}
- return service.getSilenceMode(device);
+
+ enforceBluetoothPrivilegedPermission(service);
+
+ service.mSilenceDeviceManager.setSilenceMode(device, silence);
+ return true;
+ }
+
+ @Override
+ public int getPhonebookAccessPermission(BluetoothDevice device) {
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getPhonebookAccessPermission")) {
+ return BluetoothDevice.ACCESS_UNKNOWN;
+ }
+
+ enforceBluetoothPermission(service);
+
+ return service.getDeviceAccessFromPrefs(device, PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE);
}
@Override
public boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setPhonebookAccessPermission() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setPhonebookAccessPermission")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.setPhonebookAccessPermission(device, value);
+ service.setPhonebookAccessPermission(device, value);
+ return true;
}
@Override
public int getMessageAccessPermission(BluetoothDevice device) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getMessageAccessPermission() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getMessageAccessPermission")) {
return BluetoothDevice.ACCESS_UNKNOWN;
}
- AdapterService service = getService();
- if (service == null) {
- return BluetoothDevice.ACCESS_UNKNOWN;
- }
- return service.getMessageAccessPermission(device);
+ enforceBluetoothPermission(service);
+
+ return service.getDeviceAccessFromPrefs(device, MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE);
}
@Override
public boolean setMessageAccessPermission(BluetoothDevice device, int value) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setMessageAccessPermission() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setMessageAccessPermission")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.setMessageAccessPermission(device, value);
+ enforceBluetoothPrivilegedPermission(service);
+
+ service.setMessageAccessPermission(device, value);
+ return true;
}
@Override
public int getSimAccessPermission(BluetoothDevice device) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getSimAccessPermission() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getSimAccessPermission")) {
return BluetoothDevice.ACCESS_UNKNOWN;
}
- AdapterService service = getService();
- if (service == null) {
- return BluetoothDevice.ACCESS_UNKNOWN;
- }
- return service.getSimAccessPermission(device);
+ enforceBluetoothPermission(service);
+
+ return service.getDeviceAccessFromPrefs(device, SIM_ACCESS_PERMISSION_PREFERENCE_FILE);
}
@Override
public boolean setSimAccessPermission(BluetoothDevice device, int value) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "setSimAccessPermission() - Not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "setSimAccessPermission")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
- return false;
- }
- return service.setSimAccessPermission(device, value);
- }
+ enforceBluetoothPrivilegedPermission(service);
- @Override
- public void sendConnectionStateChange(BluetoothDevice device, int profile, int state,
- int prevState) {
- AdapterService service = getService();
- if (service == null) {
- return;
- }
- service.sendConnectionStateChange(device, profile, state, prevState);
+ service.setSimAccessPermission(device, value);
+ return true;
}
@Override
@@ -1788,35 +1804,40 @@
if (service == null) {
return null;
}
- return service.getSocketManager();
+
+ return IBluetoothSocketManager.Stub.asInterface(service.mBluetoothSocketManagerBinder);
}
@Override
public boolean sdpSearch(BluetoothDevice device, ParcelUuid uuid) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "sdpSea(): not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "sdpSearch")) {
return false;
}
- AdapterService service = getService();
- if (service == null) {
+ enforceBluetoothPermission(service);
+
+ if (service.mSdpManager == null) {
return false;
}
- return service.sdpSearch(device, uuid);
+ service.mSdpManager.sdpSearch(device, uuid);
+ return true;
}
@Override
public int getBatteryLevel(BluetoothDevice device) {
- if (!Utils.checkCaller()) {
- Log.w(TAG, "getBatteryLevel(): not allowed for non-active user");
+ AdapterService service = getService();
+ if (service == null || !callerIsSystemOrActiveUser(TAG, "getBatteryLevel")) {
return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
}
- AdapterService service = getService();
- if (service == null) {
+ enforceBluetoothPermission(service);
+
+ DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) {
return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
}
- return service.getBatteryLevel(device);
+ return deviceProp.getBatteryLevel();
}
@Override
@@ -1826,6 +1847,9 @@
if (service == null) {
return AdapterProperties.MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOND;
}
+
+ enforceBluetoothPermission(service);
+
return service.getMaxConnectedAudioDevices();
}
@@ -1836,6 +1860,9 @@
if (service == null) {
return false;
}
+
+ enforceBluetoothPermission(service);
+
return service.isA2dpOffloadEnabled();
}
@@ -1845,27 +1872,34 @@
if (service == null) {
return false;
}
+
+ enforceBluetoothPrivilegedPermission(service);
+
service.disable();
- return service.factoryReset();
-
+ if (service.mDatabaseManager != null) {
+ service.mDatabaseManager.factoryReset();
+ }
+ return service.factoryResetNative();
}
@Override
- public void registerCallback(IBluetoothCallback cb) {
+ public void registerCallback(IBluetoothCallback callback) {
AdapterService service = getService();
if (service == null) {
return;
}
- service.registerCallback(cb);
+
+ service.mCallbacks.register(callback);
}
@Override
- public void unregisterCallback(IBluetoothCallback cb) {
+ public void unregisterCallback(IBluetoothCallback callback) {
AdapterService service = getService();
if (service == null) {
return;
}
- service.unregisterCallback(cb);
+
+ service.mCallbacks.unregister(callback);
}
@Override
@@ -1874,7 +1908,11 @@
if (service == null) {
return false;
}
- return service.isMultiAdvertisementSupported();
+
+ enforceBluetoothPermission(service);
+
+ int val = service.mAdapterProperties.getNumOfAdvertisementInstancesSupported();
+ return val >= MIN_ADVT_INSTANCES_FOR_MA;
}
@Override
@@ -1883,8 +1921,11 @@
if (service == null) {
return false;
}
+
+ enforceBluetoothPermission(service);
+
int val = service.getNumOfOffloadedScanFilterSupported();
- return (val >= MIN_OFFLOADED_FILTERS);
+ return val >= MIN_OFFLOADED_FILTERS;
}
@Override
@@ -1893,8 +1934,11 @@
if (service == null) {
return false;
}
+
+ enforceBluetoothPermission(service);
+
int val = service.getOffloadedScanResultStorage();
- return (val >= MIN_OFFLOADED_SCAN_STORAGE_BYTES);
+ return val >= MIN_OFFLOADED_SCAN_STORAGE_BYTES;
}
@Override
@@ -1903,6 +1947,9 @@
if (service == null) {
return false;
}
+
+ enforceBluetoothPermission(service);
+
return service.isLe2MPhySupported();
}
@@ -1912,6 +1959,9 @@
if (service == null) {
return false;
}
+
+ enforceBluetoothPermission(service);
+
return service.isLeCodedPhySupported();
}
@@ -1921,6 +1971,9 @@
if (service == null) {
return false;
}
+
+ enforceBluetoothPermission(service);
+
return service.isLeExtendedAdvertisingSupported();
}
@@ -1930,6 +1983,9 @@
if (service == null) {
return false;
}
+
+ enforceBluetoothPermission(service);
+
return service.isLePeriodicAdvertisingSupported();
}
@@ -1939,6 +1995,9 @@
if (service == null) {
return 0;
}
+
+ enforceBluetoothPermission(service);
+
return service.getLeMaximumAdvertisingDataLength();
}
@@ -1948,7 +2007,10 @@
if (service == null) {
return false;
}
- return service.isActivityAndEnergyReportingSupported();
+
+ enforceBluetoothPrivilegedPermission(service);
+
+ return service.mAdapterProperties.isActivityAndEnergyReportingSupported();
}
@Override
@@ -1957,6 +2019,9 @@
if (service == null) {
return null;
}
+
+ enforceBluetoothPrivilegedPermission(service);
+
return service.reportActivityInfo();
}
@@ -1967,7 +2032,20 @@
if (service == null) {
return false;
}
- return service.registerMetadataListener(listener, device);
+
+ if (service.mMetadataListeners == null) {
+ return false;
+ }
+ ArrayList<IBluetoothMetadataListener> list = service.mMetadataListeners.get(device);
+ if (list == null) {
+ list = new ArrayList<>();
+ } else if (list.contains(listener)) {
+ // The device is already registered with this listener
+ return true;
+ }
+ list.add(listener);
+ service.mMetadataListeners.put(device, list);
+ return true;
}
@Override
@@ -1976,7 +2054,14 @@
if (service == null) {
return false;
}
- return service.unregisterMetadataListener(device);
+
+ if (service.mMetadataListeners == null) {
+ return false;
+ }
+ if (service.mMetadataListeners.containsKey(device)) {
+ service.mMetadataListeners.remove(device);
+ }
+ return true;
}
@Override
@@ -1985,7 +2070,11 @@
if (service == null) {
return false;
}
- return service.setMetadata(device, key, value);
+
+ if (value.length > BluetoothDevice.METADATA_MAX_LENGTH) {
+ return false;
+ }
+ return service.mDatabaseManager.setCustomMeta(device, key, value);
}
@Override
@@ -1994,7 +2083,8 @@
if (service == null) {
return null;
}
- return service.getMetadata(device, key);
+
+ return service.mDatabaseManager.getCustomMeta(device, key);
}
@Override
@@ -2010,7 +2100,8 @@
if (service == null) {
return;
}
- service.onLeServiceUp();
+
+ service.mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_ON);
}
@Override
@@ -2019,7 +2110,8 @@
if (service == null) {
return;
}
- service.onBrEdrDown();
+
+ service.mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
}
@Override
@@ -2029,6 +2121,9 @@
if (service == null) {
return;
}
+
+ enforceDumpPermission(service);
+
service.dump(fd, writer, args);
writer.close();
}
@@ -2038,30 +2133,14 @@
// ----API Methods--------
- public boolean isEnabled() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mAdapterProperties.getState() == BluetoothAdapter.STATE_ON;
- }
-
public int getState() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (mAdapterProperties != null) {
return mAdapterProperties.getState();
}
return BluetoothAdapter.STATE_OFF;
}
- public boolean enable() {
- return enable(false);
- }
-
- public boolean enableNoAutoConnect() {
- return enable(true);
- }
-
public synchronized boolean enable(boolean quietMode) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
// Enforce the user restriction for disallowing Bluetooth if it was set.
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM)) {
debugLog("enable() called when Bluetooth was disallowed");
@@ -2075,69 +2154,16 @@
}
boolean disable() {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
debugLog("disable() called with mRunningProfiles.size() = " + mRunningProfiles.size());
mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_OFF);
return true;
}
- String getAddress() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- enforceCallingOrSelfPermission(LOCAL_MAC_ADDRESS_PERM, "Need LOCAL_MAC_ADDRESS permission");
-
- String addrString = null;
- byte[] address = mAdapterProperties.getAddress();
- return Utils.getAddressStringFromByte(address);
- }
-
- ParcelUuid[] getUuids() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- return mAdapterProperties.getUuids();
- }
-
public String getName() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- try {
- return mAdapterProperties.getName();
- } catch (Throwable t) {
- debugLog("getName() - Unexpected exception (" + t + ")");
- }
- return null;
+ return mAdapterProperties.getName();
}
- boolean setName(String name) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
- return mAdapterProperties.setName(name);
- }
-
- BluetoothClass getBluetoothClass() {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
- return mAdapterProperties.getBluetoothClass();
- }
-
- /**
- * Sets the Bluetooth CoD on the local adapter and also modifies the storage config for it.
- *
- * <p>Once set, this value persists across reboots.
- */
- boolean setBluetoothClass(BluetoothClass bluetoothClass) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH PRIVILEGED permission");
- debugLog("setBluetoothClass() to " + bluetoothClass);
- boolean result = mAdapterProperties.setBluetoothClass(bluetoothClass);
- if (!result) {
- Log.e(TAG, "setBluetoothClass() to " + bluetoothClass + " failed");
- }
-
- return result && storeBluetoothClassConfig(bluetoothClass.getClassOfDevice());
- }
-
- private boolean validateInputOutputCapability(int capability) {
+ private static boolean isValidIoCapability(int capability) {
if (capability < 0 || capability >= BluetoothAdapter.IO_CAPABILITY_MAX) {
Log.e(TAG, "Invalid IO capability value - " + capability);
return false;
@@ -2146,65 +2172,6 @@
return true;
}
- int getIoCapability() {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
- return mAdapterProperties.getIoCapability();
- }
-
- boolean setIoCapability(int capability) {
- enforceCallingOrSelfPermission(
- BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
- if (!validateInputOutputCapability(capability)) {
- return false;
- }
-
- return mAdapterProperties.setIoCapability(capability);
- }
-
- int getLeIoCapability() {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
- return mAdapterProperties.getLeIoCapability();
- }
-
- boolean setLeIoCapability(int capability) {
- enforceCallingOrSelfPermission(
- BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
- if (!validateInputOutputCapability(capability)) {
- return false;
- }
-
- return mAdapterProperties.setLeIoCapability(capability);
- }
-
- int getScanMode() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- return mAdapterProperties.getScanMode();
- }
-
- boolean setScanMode(int mode, int duration) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- setDiscoverableTimeout(duration);
-
- int newMode = convertScanModeToHal(mode);
- return mAdapterProperties.setScanMode(newMode);
- }
-
- int getDiscoverableTimeout() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- return mAdapterProperties.getDiscoverableTimeout();
- }
-
- boolean setDiscoverableTimeout(int timeout) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- return mAdapterProperties.setDiscoverableTimeout(timeout);
- }
-
ArrayList<DiscoveringPackage> getDiscoveringPackages() {
return mDiscoveringPackages;
}
@@ -2215,10 +2182,9 @@
}
}
- boolean startDiscovery(String callingPackage) {
+ boolean startDiscovery(String callingPackage, @Nullable String callingFeatureId) {
UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());
debugLog("startDiscovery");
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
boolean isQApp = Utils.isQApp(this, callingPackage);
String permission = null;
@@ -2227,12 +2193,14 @@
} else if (Utils.checkCallerHasNetworkSetupWizardPermission(this)) {
permission = android.Manifest.permission.NETWORK_SETUP_WIZARD;
} else if (isQApp) {
- if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingUser)) {
+ if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingFeatureId,
+ callingUser)) {
return false;
}
permission = android.Manifest.permission.ACCESS_FINE_LOCATION;
} else {
- if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingUser)) {
+ if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingFeatureId,
+ callingUser)) {
return false;
}
permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;
@@ -2244,32 +2212,12 @@
return startDiscoveryNative();
}
- boolean cancelDiscovery() {
- debugLog("cancelDiscovery");
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
-
- return cancelDiscoveryNative();
- }
-
- boolean isDiscovering() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- return mAdapterProperties.isDiscovering();
- }
-
- long getDiscoveryEndMillis() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- return mAdapterProperties.discoveryEndMillis();
- }
-
/**
* Same as API method {@link BluetoothAdapter#getBondedDevices()}
*
* @return array of bonded {@link BluetoothDevice} or null on error
*/
public BluetoothDevice[] getBondedDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.getBondedDevices();
}
@@ -2283,29 +2231,7 @@
return mDatabaseManager;
}
- int getAdapterConnectionState() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mAdapterProperties.getConnectionState();
- }
-
- int getProfileConnectionState(int profile) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- return mAdapterProperties.getProfileConnectionState(profile);
- }
-
- boolean sdpSearch(BluetoothDevice device, ParcelUuid uuid) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (mSdpManager != null) {
- mSdpManager.sdpSearch(device, uuid);
- return true;
- } else {
- return false;
- }
- }
-
boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
if (deviceProp != null && deviceProp.getBondState() != BluetoothDevice.BOND_NONE) {
return false;
@@ -2359,32 +2285,6 @@
mBondStateMachine.sendMessage(msg);
}
- boolean cancelBondProcess(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- byte[] addr = Utils.getBytesFromAddress(device.getAddress());
-
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp != null) {
- deviceProp.setBondingInitiatedLocally(false);
- }
-
- return cancelBondNative(addr);
- }
-
- boolean removeBond(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDED) {
- return false;
- }
- deviceProp.setBondingInitiatedLocally(false);
-
- Message msg = mBondStateMachine.obtainMessage(BondStateMachine.REMOVE_BOND);
- msg.obj = device;
- mBondStateMachine.sendMessage(msg);
- return true;
- }
-
/**
* Get the bond state of a particular {@link BluetoothDevice}
*
@@ -2396,7 +2296,6 @@
*/
@VisibleForTesting
public int getBondState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
if (deviceProp == null) {
return BluetoothDevice.BOND_NONE;
@@ -2404,23 +2303,63 @@
return deviceProp.getBondState();
}
- boolean isBondingInitiatedLocally(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null) {
- return false;
- }
- return deviceProp.isBondingInitiatedLocally();
- }
-
- long getSupportedProfiles() {
- return Config.getSupportedProfilesBitMask();
- }
-
int getConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- byte[] addr = Utils.getBytesFromAddress(device.getAddress());
- return getConnectionStateNative(addr);
+ return getConnectionStateNative(addressToBytes(device.getAddress()));
+ }
+
+ /**
+ * Sets device as the active devices for the profiles passed into the function
+ *
+ * @param device is the remote bluetooth device
+ * @param profiles is a constant that references for which profiles we'll be setting the remote
+ * device as our active device. One of the following:
+ * {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO},
+ * {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL}
+ * {@link BluetoothAdapter#ACTIVE_DEVICE_ALL}
+ * @return false if profiles value is not one of the constants we accept, true otherwise
+ */
+ public boolean setActiveDevice(BluetoothDevice device, @ActiveDeviceUse int profiles) {
+ enforceCallingOrSelfPermission(
+ BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
+
+ boolean setA2dp = false;
+ boolean setHeadset = false;
+
+ // Determine for which profiles we want to set device as our active device
+ switch(profiles) {
+ case BluetoothAdapter.ACTIVE_DEVICE_AUDIO:
+ setA2dp = true;
+ break;
+ case BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL:
+ setHeadset = true;
+ break;
+ case BluetoothAdapter.ACTIVE_DEVICE_ALL:
+ setA2dp = true;
+ setHeadset = true;
+ break;
+ default:
+ return false;
+ }
+
+ if (setA2dp && mA2dpService != null && mA2dpService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ Log.i(TAG, "setActiveDevice: Setting active A2dp device " + device.getAddress());
+ mA2dpService.setActiveDevice(device);
+ }
+
+ if (mHearingAidService != null && mHearingAidService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ Log.i(TAG, "setActiveDevice: Setting active Hearing Aid " + device.getAddress());
+ mHearingAidService.setActiveDevice(device);
+ }
+
+ if (setHeadset && mHeadsetService != null && mHeadsetService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ Log.i(TAG, "setActiveDevice: Setting active Headset " + device.getAddress());
+ mHeadsetService.setActiveDevice(device);
+ }
+
+ return true;
}
/**
@@ -2430,7 +2369,6 @@
* @return true if all profiles successfully connected, false if an error occurred
*/
public boolean connectAllEnabledProfiles(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (!profileServicesRunning()) {
Log.e(TAG, "connectAllEnabledProfiles: Not all profile services running");
return false;
@@ -2443,70 +2381,67 @@
int numProfilesConnected = 0;
ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
- ParcelUuid[] localDeviceUuids = getUuids();
+ ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
// All profile toggles disabled, so connects all supported profiles
if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.A2DP, device)) {
- mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
Log.i(TAG, "connectAllEnabledProfiles: Connecting A2dp");
- mA2dpService.connect(device);
+ // Set connection policy also connects the profile with CONNECTION_POLICY_ALLOWED
+ mA2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.A2DP_SINK, device)) {
- mA2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
Log.i(TAG, "connectAllEnabledProfiles: Connecting A2dp Sink");
- mA2dpSinkService.connect(device);
+ mA2dpSinkService.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HEADSET, device)) {
- mHeadsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
Log.i(TAG, "connectAllEnabledProfiles: Connecting Headset Profile");
- mHeadsetService.connect(device);
+ mHeadsetService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HEADSET_CLIENT, device)) {
- mHeadsetClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
Log.i(TAG, "connectAllEnabledProfiles: Connecting HFP");
- mHeadsetClientService.connect(device);
+ mHeadsetClientService.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.MAP_CLIENT, device)) {
- mMapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
Log.i(TAG, "connectAllEnabledProfiles: Connecting MAP");
- mMapClientService.connect(device);
+ mMapClientService.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HID_HOST, device)) {
- mHidHostService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- Log.i(TAG,
- "connectAllEnabledProfiles: Connecting Hid Host Profile");
- mHidHostService.connect(device);
+ Log.i(TAG, "connectAllEnabledProfiles: Connecting Hid Host Profile");
+ mHidHostService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.PAN, device)) {
Log.i(TAG, "connectAllEnabledProfiles: Connecting Pan Profile");
- mPanService.connect(device);
+ mPanService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.PBAP_CLIENT, device)) {
- mPbapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
Log.i(TAG, "connectAllEnabledProfiles: Connecting Pbap");
- mPbapClientService.connect(device);
+ mPbapClientService.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HEARING_AID, device)) {
- Log.i(TAG,
- "connectAllEnabledProfiles: Connecting Hearing Aid Profile");
- mHearingAidService.connect(device);
+ Log.i(TAG, "connectAllEnabledProfiles: Connecting Hearing Aid Profile");
+ mHearingAidService.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
numProfilesConnected++;
}
@@ -2523,76 +2458,53 @@
* @return true if all profiles successfully disconnected, false if an error occurred
*/
public boolean disconnectAllEnabledProfiles(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (!profileServicesRunning()) {
Log.e(TAG, "disconnectAllEnabledProfiles: Not all profile services bound");
return false;
}
ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
- ParcelUuid[] localDeviceUuids = getUuids();
+ ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
if (mA2dpService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.A2DP, device)) {
- if (mA2dpService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mA2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
-
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting A2dp");
mA2dpService.disconnect(device);
}
if (mA2dpSinkService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.A2DP_SINK, device)) {
- if (mA2dpSinkService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mA2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
-
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting A2dp Sink");
mA2dpSinkService.disconnect(device);
}
if (mHeadsetService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HEADSET, device)) {
- if (mHeadsetService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mHeadsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
-
Log.i(TAG,
"disconnectAllEnabledProfiles: Disconnecting Headset Profile");
mHeadsetService.disconnect(device);
}
if (mHeadsetClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HEADSET_CLIENT, device)) {
- if (mHeadsetClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mHeadsetClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
-
Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting HFP");
mHeadsetClientService.disconnect(device);
}
if (mMapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.MAP_CLIENT, device)) {
- if (mMapClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mMapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
-
- Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP");
+ Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP Client");
mMapClientService.disconnect(device);
}
+ if (mMapService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+ BluetoothProfile.MAP, device)) {
+ Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting MAP");
+ mMapService.disconnect(device);
+ }
if (mHidDeviceService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HID_DEVICE, device)) {
- Log.i(TAG,
- "disconnectAllEnabledProfiles: Disconnecting Hid Device "
- + "Profile");
+ Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hid Device Profile");
mHidDeviceService.disconnect(device);
}
if (mHidHostService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HID_HOST, device)) {
- if (mHidHostService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mHidHostService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
-
- Log.i(TAG,
- "disconnectAllEnabledProfiles: Disconnecting Hid Host Profile");
+ Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hid Host Profile");
mHidHostService.disconnect(device);
}
if (mPanService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
@@ -2602,17 +2514,17 @@
}
if (mPbapClientService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.PBAP_CLIENT, device)) {
- if (mPbapClientService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
- mPbapClientService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
-
- Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap");
+ Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap Client");
mPbapClientService.disconnect(device);
}
+ if (mPbapService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
+ BluetoothProfile.PBAP, device)) {
+ Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Pbap Server");
+ mPbapService.disconnect(device);
+ }
if (mHearingAidService != null && isSupported(localDeviceUuids, remoteDeviceUuids,
BluetoothProfile.HEARING_AID, device)) {
- Log.i(TAG,
- "disconnectAllEnabledProfiles: Disconnecting Hearing Aid Profile");
+ Log.i(TAG, "disconnectAllEnabledProfiles: Disconnecting Hearing Aid Profile");
mHearingAidService.disconnect(device);
}
@@ -2626,7 +2538,6 @@
* @return remote device name
*/
public String getRemoteName(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (mRemoteDevices == null) {
return null;
}
@@ -2637,44 +2548,6 @@
return deviceProp.getName();
}
- int getRemoteType(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null) {
- return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
- }
- return deviceProp.getDeviceType();
- }
-
- String getRemoteAlias(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null) {
- return null;
- }
- return deviceProp.getAlias();
- }
-
- boolean setRemoteAlias(BluetoothDevice device, String name) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null) {
- return false;
- }
- deviceProp.setAlias(device, name);
- return true;
- }
-
- int getRemoteClass(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null) {
- return 0;
- }
-
- return deviceProp.getBluetoothClass();
- }
-
/**
* Get UUIDs for service supported by a remote device
*
@@ -2683,7 +2556,6 @@
*/
@VisibleForTesting
public ParcelUuid[] getRemoteUuids(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
if (deviceProp == null) {
return null;
@@ -2691,101 +2563,26 @@
return deviceProp.getUuids();
}
- boolean fetchRemoteUuids(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- mRemoteDevices.fetchUuids(device);
- return true;
- }
-
- int getBatteryLevel(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null) {
- return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
- }
- return deviceProp.getBatteryLevel();
- }
-
- boolean setPin(BluetoothDevice device, boolean accept, int len, byte[] pinCode) {
- enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- // Only allow setting a pin in bonding state, or bonded state in case of security upgrade.
- if (deviceProp == null || (deviceProp.getBondState() != BluetoothDevice.BOND_BONDING
- && deviceProp.getBondState() != BluetoothDevice.BOND_BONDED)) {
- return false;
- }
-
- StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ void logUserBondResponse(BluetoothDevice device, boolean accepted, int event) {
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
obfuscateAddress(device), 0, device.getType(),
BluetoothDevice.BOND_BONDING,
- BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REPLIED,
- accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
- byte[] addr = Utils.getBytesFromAddress(device.getAddress());
- return pinReplyNative(addr, accept, len, pinCode);
+ event,
+ accepted ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
}
- boolean setPasskey(BluetoothDevice device, boolean accept, int len, byte[] passkey) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) {
- return false;
- }
-
- StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
- obfuscateAddress(device), 0, device.getType(),
- BluetoothDevice.BOND_BONDING,
- BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED,
- accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
- byte[] addr = Utils.getBytesFromAddress(device.getAddress());
- return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY, accept,
- Utils.byteArrayToInt(passkey));
- }
-
- boolean setPairingConfirmation(BluetoothDevice device, boolean accept) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH PRIVILEGED permission");
- DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
- if (deviceProp == null || deviceProp.getBondState() != BluetoothDevice.BOND_BONDING) {
- return false;
- }
-
- StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
- obfuscateAddress(device), 0, device.getType(),
- BluetoothDevice.BOND_BONDING,
- BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REPLIED,
- accept ? 0 : BluetoothDevice.UNBOND_REASON_AUTH_REJECTED);
- byte[] addr = Utils.getBytesFromAddress(device.getAddress());
- return sspReplyNative(addr, AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION, accept,
- 0);
- }
-
- int getPhonebookAccessPermission(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
- Context.MODE_PRIVATE);
- if (!pref.contains(device.getAddress())) {
+ int getDeviceAccessFromPrefs(BluetoothDevice device, String prefFile) {
+ SharedPreferences prefs = getSharedPreferences(prefFile, Context.MODE_PRIVATE);
+ if (!prefs.contains(device.getAddress())) {
return BluetoothDevice.ACCESS_UNKNOWN;
}
- return pref.getBoolean(device.getAddress(), false) ? BluetoothDevice.ACCESS_ALLOWED
+ return prefs.getBoolean(device.getAddress(), false)
+ ? BluetoothDevice.ACCESS_ALLOWED
: BluetoothDevice.ACCESS_REJECTED;
}
- boolean setSilenceMode(BluetoothDevice device, boolean silence) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH PRIVILEGED permission");
- mSilenceDeviceManager.setSilenceMode(device, silence);
- return true;
- }
-
- boolean getSilenceMode(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH PRIVILEGED permission");
- return mSilenceDeviceManager.getSilenceMode(device);
- }
-
- boolean setPhonebookAccessPermission(BluetoothDevice device, int value) {
- SharedPreferences pref = getSharedPreferences(PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE,
- Context.MODE_PRIVATE);
+ void setDeviceAccessFromPrefs(BluetoothDevice device, int value, String prefFile) {
+ SharedPreferences pref = getSharedPreferences(prefFile, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
if (value == BluetoothDevice.ACCESS_UNKNOWN) {
editor.remove(device.getAddress());
@@ -2793,149 +2590,55 @@
editor.putBoolean(device.getAddress(), value == BluetoothDevice.ACCESS_ALLOWED);
}
editor.apply();
- return true;
}
- int getMessageAccessPermission(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- SharedPreferences pref = getSharedPreferences(MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE,
- Context.MODE_PRIVATE);
- if (!pref.contains(device.getAddress())) {
- return BluetoothDevice.ACCESS_UNKNOWN;
- }
- return pref.getBoolean(device.getAddress(), false) ? BluetoothDevice.ACCESS_ALLOWED
- : BluetoothDevice.ACCESS_REJECTED;
+ void setPhonebookAccessPermission(BluetoothDevice device, int value) {
+ setDeviceAccessFromPrefs(device, value, PHONEBOOK_ACCESS_PERMISSION_PREFERENCE_FILE);
}
- boolean setMessageAccessPermission(BluetoothDevice device, int value) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH PRIVILEGED permission");
- SharedPreferences pref = getSharedPreferences(MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE,
- Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = pref.edit();
- if (value == BluetoothDevice.ACCESS_UNKNOWN) {
- editor.remove(device.getAddress());
- } else {
- editor.putBoolean(device.getAddress(), value == BluetoothDevice.ACCESS_ALLOWED);
- }
- editor.apply();
- return true;
+ void setMessageAccessPermission(BluetoothDevice device, int value) {
+ setDeviceAccessFromPrefs(device, value, MESSAGE_ACCESS_PERMISSION_PREFERENCE_FILE);
}
- int getSimAccessPermission(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- SharedPreferences pref =
- getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE);
- if (!pref.contains(device.getAddress())) {
- return BluetoothDevice.ACCESS_UNKNOWN;
- }
- return pref.getBoolean(device.getAddress(), false) ? BluetoothDevice.ACCESS_ALLOWED
- : BluetoothDevice.ACCESS_REJECTED;
- }
-
- boolean setSimAccessPermission(BluetoothDevice device, int value) {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
- "Need BLUETOOTH PRIVILEGED permission");
- SharedPreferences pref =
- getSharedPreferences(SIM_ACCESS_PERMISSION_PREFERENCE_FILE, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = pref.edit();
- if (value == BluetoothDevice.ACCESS_UNKNOWN) {
- editor.remove(device.getAddress());
- } else {
- editor.putBoolean(device.getAddress(), value == BluetoothDevice.ACCESS_ALLOWED);
- }
- editor.apply();
- return true;
- }
-
- void sendConnectionStateChange(BluetoothDevice device, int profile, int state, int prevState) {
- // TODO(BT) permission check?
- // Since this is a binder call check if Bluetooth is on still
- if (getState() == BluetoothAdapter.STATE_OFF) {
- return;
- }
-
- mAdapterProperties.sendConnectionStateChange(device, profile, state, prevState);
-
- }
-
- IBluetoothSocketManager getSocketManager() {
- return IBluetoothSocketManager.Stub.asInterface(mBluetoothSocketManagerBinder);
- }
-
- boolean factoryReset() {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
- if (mDatabaseManager != null) {
- mDatabaseManager.factoryReset();
- }
- return factoryResetNative();
- }
-
- void registerCallback(IBluetoothCallback cb) {
- mCallbacks.register(cb);
- }
-
- void unregisterCallback(IBluetoothCallback cb) {
- mCallbacks.unregister(cb);
- }
-
- public int getNumOfAdvertisementInstancesSupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mAdapterProperties.getNumOfAdvertisementInstancesSupported();
- }
-
- public boolean isMultiAdvertisementSupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getNumOfAdvertisementInstancesSupported() >= MIN_ADVT_INSTANCES_FOR_MA;
+ void setSimAccessPermission(BluetoothDevice device, int value) {
+ setDeviceAccessFromPrefs(device, value, SIM_ACCESS_PERMISSION_PREFERENCE_FILE);
}
public boolean isRpaOffloadSupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ enforceBluetoothPermission(this);
return mAdapterProperties.isRpaOffloadSupported();
}
public int getNumOfOffloadedIrkSupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ enforceBluetoothPermission(this);
return mAdapterProperties.getNumOfOffloadedIrkSupported();
}
public int getNumOfOffloadedScanFilterSupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.getNumOfOffloadedScanFilterSupported();
}
public int getOffloadedScanResultStorage() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.getOffloadedScanResultStorage();
}
- private boolean isActivityAndEnergyReportingSupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
- return mAdapterProperties.isActivityAndEnergyReportingSupported();
- }
-
public boolean isLe2MPhySupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.isLe2MPhySupported();
}
public boolean isLeCodedPhySupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.isLeCodedPhySupported();
}
public boolean isLeExtendedAdvertisingSupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.isLeExtendedAdvertisingSupported();
}
public boolean isLePeriodicAdvertisingSupported() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.isLePeriodicAdvertisingSupported();
}
public int getLeMaximumAdvertisingDataLength() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.getLeMaximumAdvertisingDataLength();
}
@@ -2945,7 +2648,6 @@
* @return the maximum number of connected audio devices
*/
public int getMaxConnectedAudioDevices() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.getMaxConnectedAudioDevices();
}
@@ -2955,12 +2657,10 @@
* @return true if A2DP offload is enabled
*/
public boolean isA2dpOffloadEnabled() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterProperties.isA2dpOffloadEnabled();
}
private BluetoothActivityEnergyInfo reportActivityInfo() {
- enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, "Need BLUETOOTH permission");
if (mAdapterProperties.getState() != BluetoothAdapter.STATE_ON
|| !mAdapterProperties.isActivityAndEnergyReportingSupported()) {
return null;
@@ -3008,18 +2708,10 @@
}
public int getTotalNumOfTrackableAdvertisements() {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ enforceBluetoothPermission(this);
return mAdapterProperties.getTotalNumOfTrackableAdvertisements();
}
- void onLeServiceUp() {
- mAdapterStateMachine.sendMessage(AdapterState.USER_TURN_ON);
- }
-
- void onBrEdrDown() {
- mAdapterStateMachine.sendMessage(AdapterState.BLE_TURN_OFF);
- }
-
private static int convertScanModeToHal(int mode) {
switch (mode) {
case BluetoothAdapter.SCAN_MODE_NONE:
@@ -3118,7 +2810,7 @@
energyUsed = (long) (Math.addExact(Math.addExact(txMah, rxMah), idleMah)
* getOperatingVolt());
} catch (ArithmeticException e) {
- Slog.wtf(TAG, "overflow in bluetooth energy callback", e);
+ Log.wtf(TAG, "overflow in bluetooth energy callback", e);
// Energy is already 0 if the exception was thrown.
}
}
@@ -3137,7 +2829,7 @@
} catch (ArithmeticException e) {
// This could be because we accumulated a lot of time, or we got a very strange
// value from the controller (more likely). Discard this data.
- Slog.wtf(TAG, "overflow in bluetooth energy callback", e);
+ Log.wtf(TAG, "overflow in bluetooth energy callback", e);
totalTxTimeMs = mTxTimeTotalMs;
totalRxTimeMs = mRxTimeTotalMs;
totalIdleTimeMs = mIdleTimeTotalMs;
@@ -3167,46 +2859,6 @@
+ ctrlState + "traffic = " + Arrays.toString(data));
}
- boolean registerMetadataListener(IBluetoothMetadataListener listener,
- BluetoothDevice device) {
- if (mMetadataListeners == null) {
- return false;
- }
-
- ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device);
- if (list == null) {
- list = new ArrayList<>();
- } else if (list.contains(listener)) {
- // The device is already registered with this listener
- return true;
- }
- list.add(listener);
- mMetadataListeners.put(device, list);
- return true;
- }
-
- boolean unregisterMetadataListener(BluetoothDevice device) {
- if (mMetadataListeners == null) {
- return false;
- }
- if (mMetadataListeners.containsKey(device)) {
- mMetadataListeners.remove(device);
- }
- return true;
- }
-
- boolean setMetadata(BluetoothDevice device, int key, byte[] value) {
- if (value.length > BluetoothDevice.METADATA_MAX_LENGTH) {
- Log.e(TAG, "setMetadata: value length too long " + value.length);
- return false;
- }
- return mDatabaseManager.setCustomMeta(device, key, value);
- }
-
- byte[] getMetadata(BluetoothDevice device, int key) {
- return mDatabaseManager.getCustomMeta(device, key);
- }
-
/**
* Update metadata change to registered listeners
*/
@@ -3243,8 +2895,6 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
if (args.length == 0) {
writer.println("Skipping dump in APP SERVICES, see bluetooth_manager section.");
writer.println("Use --print argument for dumpsys direct from AdapterService.");
diff --git a/src/com/android/bluetooth/btservice/AdapterState.java b/src/com/android/bluetooth/btservice/AdapterState.java
index 94feef2..8596627 100644
--- a/src/com/android/bluetooth/btservice/AdapterState.java
+++ b/src/com/android/bluetooth/btservice/AdapterState.java
@@ -20,8 +20,8 @@
import android.os.Message;
import android.util.Log;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
/**
* This state machine handles Bluetooth Adapter State.
diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java
index 3320f0e..ca18140 100644
--- a/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -26,8 +26,8 @@
import android.os.Message;
import android.os.UserHandle;
import android.util.Log;
-import android.util.StatsLog;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.a2dpsink.A2dpSinkService;
@@ -36,9 +36,9 @@
import com.android.bluetooth.hfpclient.HeadsetClientService;
import com.android.bluetooth.hid.HidHostService;
import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
import java.util.ArrayList;
import java.util.HashSet;
@@ -100,7 +100,7 @@
return bsm;
}
- public void doQuit() {
+ public synchronized void doQuit() {
quitNow();
}
@@ -122,7 +122,7 @@
}
@Override
- public boolean processMessage(Message msg) {
+ public synchronized boolean processMessage(Message msg) {
BluetoothDevice dev = (BluetoothDevice) msg.obj;
@@ -178,7 +178,7 @@
}
@Override
- public boolean processMessage(Message msg) {
+ public synchronized boolean processMessage(Message msg) {
BluetoothDevice dev = (BluetoothDevice) msg.obj;
DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
boolean result = false;
@@ -323,14 +323,14 @@
} else {
result = mAdapterService.createBondNative(addr, transport);
}
- StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
BluetoothDevice.BOND_BONDING,
oobData == null ? BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN
: BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_OOB_DATA_PROVIDED,
BluetoothProtoEnums.UNBOND_REASON_UNKNOWN);
if (!result) {
- StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
mAdapterService.obfuscateAddress(dev), transport, dev.getType(),
BluetoothDevice.BOND_NONE, BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN,
BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS);
@@ -386,12 +386,12 @@
if (oldState == newState) {
return;
}
- StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
mAdapterService.obfuscateAddress(device), 0, device.getType(),
newState, BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN, reason);
BluetoothClass deviceClass = device.getBluetoothClass();
int classOfDevice = deviceClass == null ? 0 : deviceClass.getClassOfDevice();
- StatsLog.write(StatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED,
mAdapterService.obfuscateAddress(device), classOfDevice);
mAdapterProperties.onBondStateChanged(device, newState);
@@ -491,7 +491,7 @@
device = Objects.requireNonNull(mRemoteDevices.getDevice(address));
}
- StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
mAdapterService.obfuscateAddress(device), 0, device.getType(),
BluetoothDevice.BOND_BONDING,
BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REQUESTED, 0);
@@ -514,7 +514,7 @@
bdDevice = Objects.requireNonNull(mRemoteDevices.getDevice(address));
}
- StatsLog.write(StatsLog.BLUETOOTH_BOND_STATE_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED,
mAdapterService.obfuscateAddress(bdDevice), 0, bdDevice.getType(),
BluetoothDevice.BOND_BONDING,
BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REQUESTED, 0);
@@ -538,22 +538,24 @@
PbapClientService pbapClientService = PbapClientService.getPbapClientService();
if (hidService != null) {
- hidService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+ hidService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
}
if (a2dpService != null) {
- a2dpService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+ a2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
}
if (headsetService != null) {
- headsetService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+ headsetService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
}
if (headsetClientService != null) {
- headsetClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+ headsetClientService.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
}
if (a2dpSinkService != null) {
- a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+ a2dpSinkService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
}
if (pbapClientService != null) {
- pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
+ pbapClientService.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
}
}
diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java
index 0ec5b47..f38b5ff 100644
--- a/src/com/android/bluetooth/btservice/Config.java
+++ b/src/com/android/bluetooth/btservice/Config.java
@@ -20,8 +20,9 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
+import android.os.SystemProperties;
import android.provider.Settings;
-import android.util.FeatureFlagUtils;
+import android.text.TextUtils;
import android.util.Log;
import com.android.bluetooth.R;
@@ -117,8 +118,7 @@
for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
boolean supported = resources.getBoolean(config.mSupported);
- if (!supported && (config.mClass == HearingAidService.class) && FeatureFlagUtils
- .isEnabled(ctx, FeatureFlagUtils.HEARING_AID_SETTINGS)) {
+ if (!supported && (config.mClass == HearingAidService.class) && isHearingAidSettingsEnabled(ctx)) {
Log.v(TAG, "Feature Flag enables support for HearingAidService");
supported = true;
}
@@ -160,4 +160,30 @@
return (disabledProfilesBitMask & profileMask) != 0;
}
+
+ private static boolean isHearingAidSettingsEnabled(Context context) {
+ final String flagOverridePrefix = "sys.fflag.override.";
+ final String hearingAidSettings = "settings_bluetooth_hearing_aid";
+
+ // Override precedence:
+ // Settings.Global -> sys.fflag.override.* -> static list
+
+ // Step 1: check if hearing aid flag is set in Settings.Global.
+ String value;
+ if (context != null) {
+ value = Settings.Global.getString(context.getContentResolver(), hearingAidSettings);
+ if (!TextUtils.isEmpty(value)) {
+ return Boolean.parseBoolean(value);
+ }
+ }
+
+ // Step 2: check if hearing aid flag has any override.
+ value = SystemProperties.get(flagOverridePrefix + hearingAidSettings);
+ if (!TextUtils.isEmpty(value)) {
+ return Boolean.parseBoolean(value);
+ }
+
+ // Step 3: return default value.
+ return false;
+ }
}
diff --git a/src/com/android/bluetooth/btservice/PhonePolicy.java b/src/com/android/bluetooth/btservice/PhonePolicy.java
index 39b1252..adc69cd 100644
--- a/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -20,6 +20,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.content.BroadcastReceiver;
@@ -40,6 +41,7 @@
import com.android.bluetooth.pan.PanService;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.util.HashSet;
import java.util.List;
@@ -79,6 +81,7 @@
private static final int MESSAGE_CONNECT_OTHER_PROFILES = 3;
private static final int MESSAGE_ADAPTER_STATE_TURNED_ON = 4;
private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
+ private static final int MESSAGE_DEVICE_CONNECTED = 6;
// Timeouts
@VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
@@ -115,6 +118,16 @@
BluetoothProfile.A2DP, -1, // No-op argument
intent).sendToTarget();
break;
+ case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
+ mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
+ BluetoothProfile.HEADSET, -1, // No-op argument
+ intent).sendToTarget();
+ break;
+ case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
+ mHandler.obtainMessage(MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED,
+ BluetoothProfile.HEARING_AID, -1, // No-op argument
+ intent).sendToTarget();
+ break;
case BluetoothAdapter.ACTION_STATE_CHANGED:
// Only pass the message on if the adapter has actually changed state from
// non-ON to ON. NOTE: ON is the state depicting BREDR ON and not just BLE ON.
@@ -126,6 +139,8 @@
case BluetoothDevice.ACTION_UUID:
mHandler.obtainMessage(MESSAGE_PROFILE_INIT_PRIORITIES, intent).sendToTarget();
break;
+ case BluetoothDevice.ACTION_ACL_CONNECTED:
+ mHandler.obtainMessage(MESSAGE_DEVICE_CONNECTED, intent).sendToTarget();
default:
Log.e(TAG, "Received unexpected intent, action=" + action);
break;
@@ -178,7 +193,7 @@
Intent intent = (Intent) msg.obj;
BluetoothDevice activeDevice =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- processProfileActiveDeviceChanged(activeDevice, msg.arg1);
+ processActiveDeviceChanged(activeDevice, msg.arg1);
}
break;
@@ -195,6 +210,11 @@
resetStates();
autoConnect();
break;
+ case MESSAGE_DEVICE_CONNECTED:
+ Intent intent = (Intent) msg.obj;
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ processDeviceConnected(device);
}
}
}
@@ -206,9 +226,12 @@
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
filter.addAction(BluetoothDevice.ACTION_UUID);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+ filter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
mAdapterService.registerReceiver(mReceiver, filter);
}
@@ -234,37 +257,42 @@
// Set profile priorities only for the profiles discovered on the remote device.
// This avoids needless auto-connect attempts to profiles non-existent on the remote device
- if ((hidService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid)
- || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) && (
- hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
- hidService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if ((hidService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.HID)
+ || ArrayUtils.contains(uuids, BluetoothUuid.HOGP)) && (
+ hidService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
+ hidService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
// If we do not have a stored priority for HFP/A2DP (all roles) then default to on.
- if ((headsetService != null) && ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)
- || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree)) && (
- headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED))) {
- headsetService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if ((headsetService != null) && ((ArrayUtils.contains(uuids, BluetoothUuid.HSP)
+ || ArrayUtils.contains(uuids, BluetoothUuid.HFP)) && (
+ headsetService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_UNKNOWN))) {
+ headsetService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
- if ((a2dpService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)
- || BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AdvAudioDist)) && (
- a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)) {
- a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ if ((a2dpService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.A2DP_SINK)
+ || ArrayUtils.contains(uuids, BluetoothUuid.ADV_AUDIO_DIST)) && (
+ a2dpService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
+ a2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
- if ((panService != null) && (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU) && (
- panService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)
+ if ((panService != null) && (ArrayUtils.contains(uuids, BluetoothUuid.PANU) && (
+ panService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)
&& mAdapterService.getResources()
.getBoolean(R.bool.config_bluetooth_pan_enable_autoconnect))) {
- panService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ panService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
- if ((hearingAidService != null) && BluetoothUuid.isUuidPresent(uuids,
- BluetoothUuid.HearingAid) && (hearingAidService.getPriority(device)
- == BluetoothProfile.PRIORITY_UNDEFINED)) {
+ if ((hearingAidService != null) && ArrayUtils.contains(uuids,
+ BluetoothUuid.HEARING_AID) && (hearingAidService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
debugLog("setting hearing aid profile priority for device " + device);
- hearingAidService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ hearingAidService.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
}
@@ -285,50 +313,33 @@
connectOtherProfile(device);
}
if (nextState == BluetoothProfile.STATE_DISCONNECTED) {
- handleAllProfilesDisconnected(device);
- if (prevState == BluetoothProfile.STATE_CONNECTING) {
- HeadsetService hsService = mFactory.getHeadsetService();
- boolean hsDisconnected = hsService == null
- || hsService.getConnectionState(device)
- == BluetoothProfile.STATE_DISCONNECTED;
- A2dpService a2dpService = mFactory.getA2dpService();
- boolean a2dpDisconnected = a2dpService == null
- || a2dpService.getConnectionState(device)
- == BluetoothProfile.STATE_DISCONNECTED;
- debugLog("processProfileStateChanged, device=" + device + ", a2dpDisconnected="
- + a2dpDisconnected + ", hsDisconnected=" + hsDisconnected);
- if (hsDisconnected && a2dpDisconnected) {
- removeAutoConnectFromA2dpSink(device);
- removeAutoConnectFromHeadset(device);
- }
+ if (profileId == BluetoothProfile.A2DP) {
+ mAdapterService.getDatabase().setDisconnection(device);
}
+ handleAllProfilesDisconnected(device);
}
}
}
- private void processProfileActiveDeviceChanged(BluetoothDevice activeDevice, int profileId) {
- debugLog("processProfileActiveDeviceChanged, activeDevice=" + activeDevice + ", profile="
- + profileId);
- switch (profileId) {
- // Tracking active device changed intent only for A2DP so that we always connect to a
- // single device after toggling Bluetooth
- case BluetoothProfile.A2DP:
- // Ignore null active device since we don't know if the change is triggered by
- // normal device disconnection during Bluetooth shutdown or user action
- if (activeDevice == null) {
- warnLog("processProfileActiveDeviceChanged: ignore null A2DP active device");
- return;
- }
- for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
- removeAutoConnectFromA2dpSink(device);
- removeAutoConnectFromHeadset(device);
- }
- setAutoConnectForA2dpSink(activeDevice);
- setAutoConnectForHeadset(activeDevice);
- break;
+ /**
+ * Updates the last connection date in the connection order database for the newly active device
+ * if connected to a2dp profile
+ *
+ * @param device is the device we just made the active device
+ */
+ private void processActiveDeviceChanged(BluetoothDevice device, int profileId) {
+ debugLog("processActiveDeviceChanged, device=" + device + ", profile=" + profileId);
+
+ if (device != null) {
+ mAdapterService.getDatabase().setConnection(device, profileId == BluetoothProfile.A2DP);
}
}
+ private void processDeviceConnected(BluetoothDevice device) {
+ debugLog("processDeviceConnected, device=" + device);
+ mAdapterService.getDatabase().setConnection(device, false);
+ }
+
private boolean handleAllProfilesDisconnected(BluetoothDevice device) {
boolean atLeastOneProfileConnectedForDevice = false;
boolean allProfilesEmpty = true;
@@ -381,15 +392,16 @@
if (!mAdapterService.isQuietModeEnabled()) {
debugLog("autoConnect: Initiate auto connection on BT on...");
- final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
- if (bondedDevices == null) {
- errorLog("autoConnect: bondedDevices are null");
+ final BluetoothDevice mostRecentlyActiveA2dpDevice =
+ mAdapterService.getDatabase().getMostRecentlyConnectedA2dpDevice();
+ if (mostRecentlyActiveA2dpDevice == null) {
+ errorLog("autoConnect: most recently active a2dp device is null");
return;
}
- for (BluetoothDevice device : bondedDevices) {
- autoConnectHeadset(device);
- autoConnectA2dp(device);
- }
+ debugLog("autoConnect: Device " + mostRecentlyActiveA2dpDevice
+ + " attempting auto connection");
+ autoConnectHeadset(mostRecentlyActiveA2dpDevice);
+ autoConnectA2dp(mostRecentlyActiveA2dpDevice);
} else {
debugLog("autoConnect() - BT is in quiet mode. Not initiating auto connections");
}
@@ -401,13 +413,13 @@
warnLog("autoConnectA2dp: service is null, failed to connect to " + device);
return;
}
- int a2dpPriority = a2dpService.getPriority(device);
- if (a2dpPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+ int a2dpConnectionPolicy = a2dpService.getConnectionPolicy(device);
+ if (a2dpConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
debugLog("autoConnectA2dp: connecting A2DP with " + device);
a2dpService.connect(device);
} else {
debugLog("autoConnectA2dp: skipped auto-connect A2DP with device " + device
- + " priority " + a2dpPriority);
+ + " connectionPolicy " + a2dpConnectionPolicy);
}
}
@@ -417,13 +429,13 @@
warnLog("autoConnectHeadset: service is null, failed to connect to " + device);
return;
}
- int headsetPriority = hsService.getPriority(device);
- if (headsetPriority == BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+ int headsetConnectionPolicy = hsService.getConnectionPolicy(device);
+ if (headsetConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
debugLog("autoConnectHeadset: Connecting HFP with " + device);
hsService.connect(device);
} else {
debugLog("autoConnectHeadset: skipped auto-connect HFP with device " + device
- + " priority " + headsetPriority);
+ + " connectionPolicy " + headsetConnectionPolicy);
}
}
@@ -462,8 +474,9 @@
PanService panService = mFactory.getPanService();
if (hsService != null) {
- if (!mHeadsetRetrySet.contains(device) && (hsService.getPriority(device)
- >= BluetoothProfile.PRIORITY_ON) && (hsService.getConnectionState(device)
+ if (!mHeadsetRetrySet.contains(device) && (hsService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+ && (hsService.getConnectionState(device)
== BluetoothProfile.STATE_DISCONNECTED)) {
debugLog("Retrying connection to Headset with device " + device);
mHeadsetRetrySet.add(device);
@@ -471,8 +484,9 @@
}
}
if (a2dpService != null) {
- if (!mA2dpRetrySet.contains(device) && (a2dpService.getPriority(device)
- >= BluetoothProfile.PRIORITY_ON) && (a2dpService.getConnectionState(device)
+ if (!mA2dpRetrySet.contains(device) && (a2dpService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+ && (a2dpService.getConnectionState(device)
== BluetoothProfile.STATE_DISCONNECTED)) {
debugLog("Retrying connection to A2DP with device " + device);
mA2dpRetrySet.add(device);
@@ -483,8 +497,9 @@
List<BluetoothDevice> panConnDevList = panService.getConnectedDevices();
// TODO: the panConnDevList.isEmpty() check below should be removed once
// Multi-PAN is supported.
- if (panConnDevList.isEmpty() && (panService.getPriority(device)
- >= BluetoothProfile.PRIORITY_ON) && (panService.getConnectionState(device)
+ if (panConnDevList.isEmpty() && (panService.getConnectionPolicy(device)
+ == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+ && (panService.getConnectionState(device)
== BluetoothProfile.STATE_DISCONNECTED)) {
debugLog("Retrying connection to PAN with device " + device);
panService.connect(device);
@@ -492,77 +507,6 @@
}
}
- /**
- * Set a device's headset profile priority to PRIORITY_AUTO_CONNECT if device support that
- * profile
- *
- * @param device device whose headset profile priority should be PRIORITY_AUTO_CONNECT
- */
- private void setAutoConnectForHeadset(BluetoothDevice device) {
- HeadsetService hsService = mFactory.getHeadsetService();
- if (hsService == null) {
- warnLog("setAutoConnectForHeadset: HEADSET service is null");
- return;
- }
- if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
- debugLog("setAutoConnectForHeadset: device " + device + " PRIORITY_AUTO_CONNECT");
- hsService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
- }
- }
-
- /**
- * Set a device's A2DP profile priority to PRIORITY_AUTO_CONNECT if device support that profile
- *
- * @param device device whose headset profile priority should be PRIORITY_AUTO_CONNECT
- */
- private void setAutoConnectForA2dpSink(BluetoothDevice device) {
- A2dpService a2dpService = mFactory.getA2dpService();
- if (a2dpService == null) {
- warnLog("setAutoConnectForA2dpSink: A2DP service is null");
- return;
- }
- if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
- debugLog("setAutoConnectForA2dpSink: device " + device + " PRIORITY_AUTO_CONNECT");
- a2dpService.setPriority(device, BluetoothProfile.PRIORITY_AUTO_CONNECT);
- }
- }
-
- /**
- * Remove PRIORITY_AUTO_CONNECT from all headsets and set headset that used to have
- * PRIORITY_AUTO_CONNECT to PRIORITY_ON
- *
- * @param device device whose PRIORITY_AUTO_CONNECT priority should be removed
- */
- private void removeAutoConnectFromHeadset(BluetoothDevice device) {
- HeadsetService hsService = mFactory.getHeadsetService();
- if (hsService == null) {
- warnLog("removeAutoConnectFromHeadset: HEADSET service is null");
- return;
- }
- if (hsService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT) {
- debugLog("removeAutoConnectFromHeadset: device " + device + " PRIORITY_ON");
- hsService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
- }
-
- /**
- * Remove PRIORITY_AUTO_CONNECT from all A2DP sinks and set A2DP sink that used to have
- * PRIORITY_AUTO_CONNECT to PRIORITY_ON
- *
- * @param device device whose PRIORITY_AUTO_CONNECT priority should be removed
- */
- private void removeAutoConnectFromA2dpSink(BluetoothDevice device) {
- A2dpService a2dpService = mFactory.getA2dpService();
- if (a2dpService == null) {
- warnLog("removeAutoConnectFromA2dpSink: A2DP service is null");
- return;
- }
- if (a2dpService.getPriority(device) >= BluetoothProfile.PRIORITY_AUTO_CONNECT) {
- debugLog("removeAutoConnectFromA2dpSink: device " + device + " PRIORITY_ON");
- a2dpService.setPriority(device, BluetoothProfile.PRIORITY_ON);
- }
- }
-
private static void debugLog(String msg) {
if (DBG) {
Log.i(TAG, msg);
diff --git a/src/com/android/bluetooth/btservice/ProfileObserver.java b/src/com/android/bluetooth/btservice/ProfileObserver.java
deleted file mode 100644
index d5ddff4..0000000
--- a/src/com/android/bluetooth/btservice/ProfileObserver.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.android.bluetooth.btservice;
-
-import android.bluetooth.BluetoothAdapter;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings;
-
-/**
- * This helper class monitors the state of the enabled profiles and will update and restart
- * the adapter when necessary.
- */
-public class ProfileObserver extends ContentObserver {
- private Context mContext;
- private AdapterService mService;
- private AdapterStateObserver mStateObserver;
-
- public ProfileObserver(Context context, AdapterService service, Handler handler) {
- super(handler);
- mContext = context;
- mService = service;
- mStateObserver = new AdapterStateObserver(this);
- }
-
- public void start() {
- mContext.getContentResolver()
- .registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.BLUETOOTH_DISABLED_PROFILES),
- false, this);
- }
-
- private void onBluetoothOff() {
- mContext.unregisterReceiver(mStateObserver);
- Config.init(mContext);
- mService.enable();
- }
-
- public void stop() {
- mContext.getContentResolver().unregisterContentObserver(this);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- if (mService.isEnabled()) {
- mContext.registerReceiver(mStateObserver,
- new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
- mService.disable();
- }
- }
-
- private static class AdapterStateObserver extends BroadcastReceiver {
- private ProfileObserver mProfileObserver;
-
- AdapterStateObserver(ProfileObserver observer) {
- mProfileObserver = observer;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())
- && intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
- == BluetoothAdapter.STATE_OFF) {
- mProfileObserver.onBluetoothOff();
- }
- }
- }
-}
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 2965715..abbb677 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -31,8 +31,8 @@
import android.os.Message;
import android.os.ParcelUuid;
import android.util.Log;
-import android.util.StatsLog;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.hfp.HeadsetHalConstants;
@@ -311,10 +311,11 @@
/**
* @param mBondState the mBondState to set
*/
- void setBondState(int mBondState) {
+ void setBondState(int newBondState) {
synchronized (mObject) {
- this.mBondState = mBondState;
- if (mBondState == BluetoothDevice.BOND_NONE) {
+ if ((mBondState == BluetoothDevice.BOND_BONDED
+ && newBondState == BluetoothDevice.BOND_BONDING)
+ || newBondState == BluetoothDevice.BOND_NONE) {
/* Clearing the Uuids local copy when the device is unpaired. If not cleared,
cachedBluetoothDevice issued a connect using the local cached copy of uuids,
without waiting for the ACTION_UUID intent.
@@ -322,6 +323,7 @@
mUuids = null;
mAlias = null;
}
+ mBondState = newBondState;
}
}
@@ -334,6 +336,14 @@
}
}
+ boolean isBonding() {
+ return getBondState() == BluetoothDevice.BOND_BONDING;
+ }
+
+ boolean isBondingOrBonded() {
+ return isBonding() || getBondState() == BluetoothDevice.BOND_BONDED;
+ }
+
/**
* @param isBondingInitiatedLocally wether bonding is initiated locally
*/
@@ -642,11 +652,11 @@
int connectionState = newState == AbstractionLayer.BT_ACL_STATE_CONNECTED
? BluetoothAdapter.STATE_CONNECTED : BluetoothAdapter.STATE_DISCONNECTED;
- StatsLog.write(StatsLog.BLUETOOTH_ACL_CONNECTION_STATE_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACL_CONNECTION_STATE_CHANGED,
sAdapterService.obfuscateAddress(device), connectionState);
BluetoothClass deviceClass = device.getBluetoothClass();
int classOfDevice = deviceClass == null ? 0 : deviceClass.getClassOfDevice();
- StatsLog.write(StatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED,
sAdapterService.obfuscateAddress(device), classOfDevice);
if (intent != null) {
diff --git a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index f86adbd..1eb3762 100644
--- a/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -17,6 +17,8 @@
package com.android.bluetooth.btservice.storage;
import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -33,8 +35,8 @@
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
-import android.util.StatsLog;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.internal.annotations.VisibleForTesting;
@@ -42,9 +44,11 @@
import com.google.common.collect.EvictingQueue;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Semaphore;
@@ -76,6 +80,33 @@
private static final int MSG_CLEAR_DATABASE = 100;
private static final String LOCAL_STORAGE = "LocalStorage";
+ private static final String
+ LEGACY_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
+ private static final String
+ LEGACY_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
+ private static final String
+ LEGACY_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_";
+ private static final String
+ LEGACY_A2DP_SRC_PRIORITY_PREFIX = "bluetooth_a2dp_src_priority_";
+ private static final String LEGACY_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX =
+ "bluetooth_a2dp_supports_optional_codecs_";
+ private static final String LEGACY_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX =
+ "bluetooth_a2dp_optional_codecs_enabled_";
+ private static final String
+ LEGACY_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_";
+ private static final String
+ LEGACY_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_";
+ private static final String
+ LEGACY_MAP_CLIENT_PRIORITY_PREFIX = "bluetooth_map_client_priority_";
+ private static final String
+ LEGACY_PBAP_CLIENT_PRIORITY_PREFIX = "bluetooth_pbap_client_priority_";
+ private static final String
+ LEGACY_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_";
+ private static final String
+ LEGACY_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_";
+ private static final String
+ LEGACY_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_";
+
/**
* Constructor of the DatabaseManager
*/
@@ -103,6 +134,7 @@
.createDatabaseWithoutMigration(mAdapterService);
list = mDatabase.load();
}
+ compactLastConnectionTime(list);
cacheMetadata(list);
}
break;
@@ -170,7 +202,7 @@
if (mMetadataCache.containsKey(address)) {
return;
}
- createMetadata(address);
+ createMetadata(address, false);
} else {
Metadata metadata = mMetadataCache.get(address);
if (metadata != null) {
@@ -223,7 +255,7 @@
String address = device.getAddress();
if (!mMetadataCache.containsKey(address)) {
- createMetadata(address);
+ createMetadata(address, false);
}
Metadata data = mMetadataCache.get(address);
byte[] oldValue = data.getCustomizedMeta(key);
@@ -269,7 +301,7 @@
}
/**
- * Set the device profile prioirty
+ * Set the device profile connection policy
*
* @param device {@link BluetoothDevice} wish to set
* @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
@@ -279,53 +311,53 @@
* {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
* {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
* {@link BluetoothProfile#HEARING_AID}
- * @param newPriority the priority to set; one of
- * {@link BluetoothProfile#PRIORITY_UNDEFINED},
- * {@link BluetoothProfile#PRIORITY_OFF},
- * {@link BluetoothProfile#PRIORITY_ON},
- * {@link BluetoothProfile#PRIORITY_AUTO_CONNECT}
+ * @param newConnectionPolicy the connectionPolicy to set; one of
+ * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
+ * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED}
*/
@VisibleForTesting
- public boolean setProfilePriority(BluetoothDevice device, int profile, int newPriority) {
+ public boolean setProfileConnectionPolicy(BluetoothDevice device, int profile,
+ int newConnectionPolicy) {
synchronized (mMetadataCache) {
if (device == null) {
- Log.e(TAG, "setProfilePriority: device is null");
+ Log.e(TAG, "setProfileConnectionPolicy: device is null");
return false;
}
- if (newPriority != BluetoothProfile.PRIORITY_UNDEFINED
- && newPriority != BluetoothProfile.PRIORITY_OFF
- && newPriority != BluetoothProfile.PRIORITY_ON
- && newPriority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
- Log.e(TAG, "setProfilePriority: invalid priority " + newPriority);
+ if (newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+ && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && newConnectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ Log.e(TAG, "setProfileConnectionPolicy: invalid connection policy "
+ + newConnectionPolicy);
return false;
}
String address = device.getAddress();
if (!mMetadataCache.containsKey(address)) {
- if (newPriority == BluetoothProfile.PRIORITY_UNDEFINED) {
+ if (newConnectionPolicy == BluetoothProfile.CONNECTION_POLICY_UNKNOWN) {
return true;
}
- createMetadata(address);
+ createMetadata(address, false);
}
Metadata data = mMetadataCache.get(address);
- int oldPriority = data.getProfilePriority(profile);
- if (oldPriority == newPriority) {
- Log.v(TAG, "setProfilePriority priority not changed.");
+ int oldConnectionPolicy = data.getProfileConnectionPolicy(profile);
+ if (oldConnectionPolicy == newConnectionPolicy) {
+ Log.v(TAG, "setProfileConnectionPolicy connection policy not changed.");
return true;
}
String profileStr = BluetoothProfile.getProfileName(profile);
- logMetadataChange(address, profileStr + " priority changed: "
- + ": " + oldPriority + " -> " + newPriority);
+ logMetadataChange(address, profileStr + " connection policy changed: "
+ + ": " + oldConnectionPolicy + " -> " + newConnectionPolicy);
- data.setProfilePriority(profile, newPriority);
+ data.setProfileConnectionPolicy(profile, newConnectionPolicy);
updateDatabase(data);
return true;
}
}
/**
- * Get the device profile prioirty
+ * Get the device profile connection policy
*
* @param device {@link BluetoothDevice} wish to get
* @param profile The Bluetooth profile; one of {@link BluetoothProfile#HEADSET},
@@ -335,32 +367,32 @@
* {@link BluetoothProfile#PBAP_CLIENT}, {@link BluetoothProfile#MAP},
* {@link BluetoothProfile#MAP_CLIENT}, {@link BluetoothProfile#SAP},
* {@link BluetoothProfile#HEARING_AID}
- * @return the profile priority of the device; one of
- * {@link BluetoothProfile#PRIORITY_UNDEFINED},
- * {@link BluetoothProfile#PRIORITY_OFF},
- * {@link BluetoothProfile#PRIORITY_ON},
- * {@link BluetoothProfile#PRIORITY_AUTO_CONNECT}
+ * @return the profile connection policy of the device; one of
+ * {@link BluetoothProfile.CONNECTION_POLICY_UNKNOWN},
+ * {@link BluetoothProfile.CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile.CONNECTION_POLICY_ALLOWED}
*/
@VisibleForTesting
- public int getProfilePriority(BluetoothDevice device, int profile) {
+ public int getProfileConnectionPolicy(BluetoothDevice device, int profile) {
synchronized (mMetadataCache) {
if (device == null) {
- Log.e(TAG, "getProfilePriority: device is null");
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ Log.e(TAG, "getProfileConnectionPolicy: device is null");
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
String address = device.getAddress();
if (!mMetadataCache.containsKey(address)) {
- Log.d(TAG, "getProfilePriority: device " + address + " is not in cache");
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ Log.d(TAG, "getProfileConnectionPolicy: device " + address + " is not in cache");
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
Metadata data = mMetadataCache.get(address);
- int priority = data.getProfilePriority(profile);
- Log.v(TAG, "getProfilePriority: " + address + ", profile=" + profile
- + ", priority = " + priority);
- return priority;
+ int connectionPolicy = data.getProfileConnectionPolicy(profile);
+
+ Log.v(TAG, "getProfileConnectionPolicy: " + address + ", profile=" + profile
+ + ", connectionPolicy = " + connectionPolicy);
+ return connectionPolicy;
}
}
@@ -415,6 +447,7 @@
* {@link BluetoothA2dp#OPTIONAL_CODECS_SUPPORTED},
*/
@VisibleForTesting
+ @OptionalCodecsSupportStatus
public int getA2dpSupportsOptionalCodecs(BluetoothDevice device) {
synchronized (mMetadataCache) {
if (device == null) {
@@ -485,6 +518,7 @@
* {@link BluetoothA2dp#OPTIONAL_CODECS_PREF_ENABLED}
*/
@VisibleForTesting
+ @OptionalCodecsPreferenceStatus
public int getA2dpOptionalCodecsEnabled(BluetoothDevice device) {
synchronized (mMetadataCache) {
if (device == null) {
@@ -505,6 +539,159 @@
}
/**
+ * Updates the time this device was last connected
+ *
+ * @param device is the remote bluetooth device for which we are setting the connection time
+ */
+ public void setConnection(BluetoothDevice device, boolean isA2dpDevice) {
+ synchronized (mMetadataCache) {
+ Log.d(TAG, "setConnection: device=" + device + " and isA2dpDevice=" + isA2dpDevice);
+ if (device == null) {
+ Log.e(TAG, "setConnection: device is null");
+ return;
+ }
+
+ if (isA2dpDevice) {
+ resetActiveA2dpDevice();
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ Log.d(TAG, "setConnection: Creating new metadata entry for device: " + device);
+ createMetadata(address, isA2dpDevice);
+ return;
+ }
+ // Updates last_active_time to the current counter value and increments the counter
+ Metadata metadata = mMetadataCache.get(address);
+ metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
+
+ // Only update is_active_a2dp_device if an a2dp device is connected
+ if (isA2dpDevice) {
+ metadata.is_active_a2dp_device = true;
+ }
+
+ Log.d(TAG, "Updating last connected time for device: " + device + " to "
+ + metadata.last_active_time);
+ updateDatabase(metadata);
+ }
+ }
+
+ /**
+ * Sets is_active_device to false if currently true for device
+ *
+ * @param device is the remote bluetooth device with which we have disconnected a2dp
+ */
+ public void setDisconnection(BluetoothDevice device) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "setDisconnection: device is null");
+ return;
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ return;
+ }
+ // Updates last connected time to either current time if connected or -1 if disconnected
+ Metadata metadata = mMetadataCache.get(address);
+ if (metadata.is_active_a2dp_device) {
+ metadata.is_active_a2dp_device = false;
+ Log.d(TAG, "setDisconnection: Updating is_active_device to false for device: "
+ + device);
+ updateDatabase(metadata);
+ }
+ }
+ }
+
+ /**
+ * Remove a2dpActiveDevice from the current active device in the connection order table
+ */
+ private void resetActiveA2dpDevice() {
+ synchronized (mMetadataCache) {
+ Log.d(TAG, "resetActiveA2dpDevice()");
+ for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
+ Metadata metadata = entry.getValue();
+ if (metadata.is_active_a2dp_device) {
+ Log.d(TAG, "resetActiveA2dpDevice");
+ metadata.is_active_a2dp_device = false;
+ updateDatabase(metadata);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the most recently connected bluetooth devices in order with most recently connected
+ * first and least recently connected last
+ *
+ * @return a {@link List} of {@link BluetoothDevice} representing connected bluetooth devices
+ * in order of most recently connected
+ */
+ public List<BluetoothDevice> getMostRecentlyConnectedDevices() {
+ List<BluetoothDevice> mostRecentlyConnectedDevices = new ArrayList<>();
+ synchronized (mMetadataCache) {
+ List<Metadata> sortedMetadata = new ArrayList<>(mMetadataCache.values());
+ sortedMetadata.sort((o1, o2) -> Long.compare(o2.last_active_time, o1.last_active_time));
+ for (Metadata metadata : sortedMetadata) {
+ try {
+ mostRecentlyConnectedDevices.add(BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice(metadata.getAddress()));
+ } catch (IllegalArgumentException ex) {
+ Log.d(TAG, "getBondedDevicesOrdered: Invalid address for "
+ + "device " + metadata.getAddress());
+ }
+ }
+ }
+ return mostRecentlyConnectedDevices;
+ }
+
+ /**
+ * Gets the last active a2dp device
+ *
+ * @return the most recently active a2dp device or null if the last a2dp device was null
+ */
+ public BluetoothDevice getMostRecentlyConnectedA2dpDevice() {
+ synchronized (mMetadataCache) {
+ for (Map.Entry<String, Metadata> entry : mMetadataCache.entrySet()) {
+ Metadata metadata = entry.getValue();
+ if (metadata.is_active_a2dp_device) {
+ try {
+ return BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+ metadata.getAddress());
+ } catch (IllegalArgumentException ex) {
+ Log.d(TAG, "getMostRecentlyConnectedA2dpDevice: Invalid address for "
+ + "device " + metadata.getAddress());
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ *
+ * @param metadataList is the list of metadata
+ */
+ private void compactLastConnectionTime(List<Metadata> metadataList) {
+ Log.d(TAG, "compactLastConnectionTime: Compacting metadata after load");
+ MetadataDatabase.sCurrentConnectionNumber = 0;
+ // Have to go in reverse order as list is ordered by descending last_active_time
+ for (int index = metadataList.size() - 1; index >= 0; index--) {
+ Metadata metadata = metadataList.get(index);
+ if (metadata.last_active_time != MetadataDatabase.sCurrentConnectionNumber) {
+ Log.d(TAG, "compactLastConnectionTime: Setting last_active_item for device: "
+ + metadata.getAddress() + " from " + metadata.last_active_time + " to "
+ + MetadataDatabase.sCurrentConnectionNumber);
+ metadata.last_active_time = MetadataDatabase.sCurrentConnectionNumber;
+ updateDatabase(metadata);
+ MetadataDatabase.sCurrentConnectionNumber++;
+ }
+ }
+ }
+
+ /**
* Get the {@link Looper} for the handler thread. This is used in testing and helper
* objects
*
@@ -577,8 +764,9 @@
mMetadataCache.clear();
}
- void createMetadata(String address) {
+ void createMetadata(String address, boolean isActiveA2dpDevice) {
Metadata data = new Metadata(address);
+ data.is_active_a2dp_device = isActiveA2dpDevice;
mMetadataCache.put(address, data);
updateDatabase(data);
logMetadataChange(address, "Metadata created");
@@ -641,63 +829,66 @@
ContentResolver contentResolver = mAdapterService.getContentResolver();
for (BluetoothDevice device : bondedDevices) {
- int a2dpPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int a2dpSinkPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothA2dpSrcPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int hearingaidPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int headsetPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int headsetClientPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothHeadsetPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int hidHostPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothHidHostPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int mapPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothMapPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int mapClientPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothMapClientPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int panPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothPanPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int pbapPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int pbapClientPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
- int sapPriority = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothSapPriorityKey(device.getAddress()),
- BluetoothProfile.PRIORITY_UNDEFINED);
+ int a2dpConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyA2dpSinkPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int a2dpSinkConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyA2dpSrcPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int hearingaidConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyHearingAidPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int headsetConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyHeadsetPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int headsetClientConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyHeadsetPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int hidHostConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyHidHostPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int mapConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyMapPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int mapClientConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyMapClientPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int panConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyPanPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int pbapConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyPbapClientPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int pbapClientConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacyPbapClientPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ int sapConnectionPolicy = Settings.Global.getInt(contentResolver,
+ getLegacySapPriorityKey(device.getAddress()),
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
int a2dpSupportsOptionalCodec = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothA2dpSupportsOptionalCodecsKey(device.getAddress()),
+ getLegacyA2dpSupportsOptionalCodecsKey(device.getAddress()),
BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
int a2dpOptionalCodecEnabled = Settings.Global.getInt(contentResolver,
- Settings.Global.getBluetoothA2dpOptionalCodecsEnabledKey(device.getAddress()),
+ getLegacyA2dpOptionalCodecsEnabledKey(device.getAddress()),
BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
String address = device.getAddress();
Metadata data = new Metadata(address);
- data.setProfilePriority(BluetoothProfile.A2DP, a2dpPriority);
- data.setProfilePriority(BluetoothProfile.A2DP_SINK, a2dpSinkPriority);
- data.setProfilePriority(BluetoothProfile.HEADSET, headsetPriority);
- data.setProfilePriority(BluetoothProfile.HEADSET_CLIENT, headsetClientPriority);
- data.setProfilePriority(BluetoothProfile.HID_HOST, hidHostPriority);
- data.setProfilePriority(BluetoothProfile.PAN, panPriority);
- data.setProfilePriority(BluetoothProfile.PBAP, pbapPriority);
- data.setProfilePriority(BluetoothProfile.PBAP_CLIENT, pbapClientPriority);
- data.setProfilePriority(BluetoothProfile.MAP, mapPriority);
- data.setProfilePriority(BluetoothProfile.MAP_CLIENT, mapClientPriority);
- data.setProfilePriority(BluetoothProfile.SAP, sapPriority);
- data.setProfilePriority(BluetoothProfile.HEARING_AID, hearingaidPriority);
+ data.setProfileConnectionPolicy(BluetoothProfile.A2DP, a2dpConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.A2DP_SINK, a2dpSinkConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.HEADSET, headsetConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.HEADSET_CLIENT,
+ headsetClientConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.HID_HOST, hidHostConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.PAN, panConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.PBAP, pbapConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.PBAP_CLIENT,
+ pbapClientConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.MAP, mapConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.MAP_CLIENT, mapClientConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.SAP, sapConnectionPolicy);
+ data.setProfileConnectionPolicy(BluetoothProfile.HEARING_AID,
+ hearingaidConnectionPolicy);
data.a2dpSupportsOptionalCodecs = a2dpSupportsOptionalCodec;
data.a2dpOptionalCodecsEnabled = a2dpOptionalCodecEnabled;
mMetadataCache.put(address, data);
@@ -715,6 +906,93 @@
}
+ /**
+ * Get the key that retrieves a bluetooth headset's priority.
+ */
+ private static String getLegacyHeadsetPriorityKey(String address) {
+ return LEGACY_HEADSET_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth a2dp sink's priority.
+ */
+ private static String getLegacyA2dpSinkPriorityKey(String address) {
+ return LEGACY_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth a2dp src's priority.
+ */
+ private static String getLegacyA2dpSrcPriorityKey(String address) {
+ return LEGACY_A2DP_SRC_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth a2dp device's ability to support optional codecs.
+ */
+ private static String getLegacyA2dpSupportsOptionalCodecsKey(String address) {
+ return LEGACY_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX
+ + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves whether a bluetooth a2dp device should have optional codecs
+ * enabled.
+ */
+ private static String getLegacyA2dpOptionalCodecsEnabledKey(String address) {
+ return LEGACY_A2DP_OPTIONAL_CODECS_ENABLED_PREFIX
+ + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth Input Device's priority.
+ */
+ private static String getLegacyHidHostPriorityKey(String address) {
+ return LEGACY_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth pan client priority.
+ */
+ private static String getLegacyPanPriorityKey(String address) {
+ return LEGACY_PAN_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth hearing aid priority.
+ */
+ private static String getLegacyHearingAidPriorityKey(String address) {
+ return LEGACY_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth map priority.
+ */
+ private static String getLegacyMapPriorityKey(String address) {
+ return LEGACY_MAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth map client priority.
+ */
+ private static String getLegacyMapClientPriorityKey(String address) {
+ return LEGACY_MAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth pbap client priority.
+ */
+ private static String getLegacyPbapClientPriorityKey(String address) {
+ return LEGACY_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Get the key that retrieves a bluetooth sap priority.
+ */
+ private static String getLegacySapPriorityKey(String address) {
+ return LEGACY_SAP_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+ }
+
private void loadDatabase() {
Log.d(TAG, "Load Database");
Message message = mHandler.obtainMessage(MSG_LOAD_DATABASE);
@@ -738,7 +1016,8 @@
mHandler.sendMessage(message);
}
- private void deleteDatabase(Metadata data) {
+ @VisibleForTesting
+ void deleteDatabase(Metadata data) {
String address = data.getAddress();
if (address == null) {
Log.e(TAG, "deleteDatabase: address is null");
@@ -775,7 +1054,7 @@
// Do not log anything if metadata doesn't fall into above categories
return;
}
- StatsLog.write(StatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
mAdapterService.obfuscateAddress(device),
BluetoothProtoEnums.DEVICE_INFO_EXTERNAL, callingApp, manufacturerName, modelName,
hardwareVersion, softwareVersion);
diff --git a/src/com/android/bluetooth/btservice/storage/Metadata.java b/src/com/android/bluetooth/btservice/storage/Metadata.java
index 9151ede..43f618a 100644
--- a/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -17,6 +17,8 @@
package com.android.bluetooth.btservice.storage;
import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -37,99 +39,110 @@
public boolean migrated;
@Embedded
- public ProfilePrioritiesEntity profilePriorities;
+ public ProfilePrioritiesEntity profileConnectionPolicies;
@Embedded
@NonNull
public CustomizedMetadataEntity publicMetadata;
- public int a2dpSupportsOptionalCodecs;
- public int a2dpOptionalCodecsEnabled;
+ public @OptionalCodecsSupportStatus int a2dpSupportsOptionalCodecs;
+ public @OptionalCodecsPreferenceStatus int a2dpOptionalCodecsEnabled;
+
+ public long last_active_time;
+ public boolean is_active_a2dp_device;
Metadata(String address) {
this.address = address;
migrated = false;
- profilePriorities = new ProfilePrioritiesEntity();
+ profileConnectionPolicies = new ProfilePrioritiesEntity();
publicMetadata = new CustomizedMetadataEntity();
a2dpSupportsOptionalCodecs = BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN;
a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+ last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
+ is_active_a2dp_device = true;
}
String getAddress() {
return address;
}
- void setProfilePriority(int profile, int priority) {
+ void setProfileConnectionPolicy(int profile, int connectionPolicy) {
+ // We no longer support BluetoothProfile.PRIORITY_AUTO_CONNECT and are merging it into
+ // BluetoothProfile.CONNECTION_POLICY_ALLOWED
+ if (connectionPolicy > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+ }
+
switch (profile) {
case BluetoothProfile.A2DP:
- profilePriorities.a2dp_priority = priority;
+ profileConnectionPolicies.a2dp_connection_policy = connectionPolicy;
break;
case BluetoothProfile.A2DP_SINK:
- profilePriorities.a2dp_sink_priority = priority;
+ profileConnectionPolicies.a2dp_sink_connection_policy = connectionPolicy;
break;
case BluetoothProfile.HEADSET:
- profilePriorities.hfp_priority = priority;
+ profileConnectionPolicies.hfp_connection_policy = connectionPolicy;
break;
case BluetoothProfile.HEADSET_CLIENT:
- profilePriorities.hfp_client_priority = priority;
+ profileConnectionPolicies.hfp_client_connection_policy = connectionPolicy;
break;
case BluetoothProfile.HID_HOST:
- profilePriorities.hid_host_priority = priority;
+ profileConnectionPolicies.hid_host_connection_policy = connectionPolicy;
break;
case BluetoothProfile.PAN:
- profilePriorities.pan_priority = priority;
+ profileConnectionPolicies.pan_connection_policy = connectionPolicy;
break;
case BluetoothProfile.PBAP:
- profilePriorities.pbap_priority = priority;
+ profileConnectionPolicies.pbap_connection_policy = connectionPolicy;
break;
case BluetoothProfile.PBAP_CLIENT:
- profilePriorities.pbap_client_priority = priority;
+ profileConnectionPolicies.pbap_client_connection_policy = connectionPolicy;
break;
case BluetoothProfile.MAP:
- profilePriorities.map_priority = priority;
+ profileConnectionPolicies.map_connection_policy = connectionPolicy;
break;
case BluetoothProfile.MAP_CLIENT:
- profilePriorities.map_client_priority = priority;
+ profileConnectionPolicies.map_client_connection_policy = connectionPolicy;
break;
case BluetoothProfile.SAP:
- profilePriorities.sap_priority = priority;
+ profileConnectionPolicies.sap_connection_policy = connectionPolicy;
break;
case BluetoothProfile.HEARING_AID:
- profilePriorities.hearing_aid_priority = priority;
+ profileConnectionPolicies.hearing_aid_connection_policy = connectionPolicy;
break;
default:
throw new IllegalArgumentException("invalid profile " + profile);
}
}
- int getProfilePriority(int profile) {
+ int getProfileConnectionPolicy(int profile) {
switch (profile) {
case BluetoothProfile.A2DP:
- return profilePriorities.a2dp_priority;
+ return profileConnectionPolicies.a2dp_connection_policy;
case BluetoothProfile.A2DP_SINK:
- return profilePriorities.a2dp_sink_priority;
+ return profileConnectionPolicies.a2dp_sink_connection_policy;
case BluetoothProfile.HEADSET:
- return profilePriorities.hfp_priority;
+ return profileConnectionPolicies.hfp_connection_policy;
case BluetoothProfile.HEADSET_CLIENT:
- return profilePriorities.hfp_client_priority;
+ return profileConnectionPolicies.hfp_client_connection_policy;
case BluetoothProfile.HID_HOST:
- return profilePriorities.hid_host_priority;
+ return profileConnectionPolicies.hid_host_connection_policy;
case BluetoothProfile.PAN:
- return profilePriorities.pan_priority;
+ return profileConnectionPolicies.pan_connection_policy;
case BluetoothProfile.PBAP:
- return profilePriorities.pbap_priority;
+ return profileConnectionPolicies.pbap_connection_policy;
case BluetoothProfile.PBAP_CLIENT:
- return profilePriorities.pbap_client_priority;
+ return profileConnectionPolicies.pbap_client_connection_policy;
case BluetoothProfile.MAP:
- return profilePriorities.map_priority;
+ return profileConnectionPolicies.map_connection_policy;
case BluetoothProfile.MAP_CLIENT:
- return profilePriorities.map_client_priority;
+ return profileConnectionPolicies.map_client_connection_policy;
case BluetoothProfile.SAP:
- return profilePriorities.sap_priority;
+ return profileConnectionPolicies.sap_connection_policy;
case BluetoothProfile.HEARING_AID:
- return profilePriorities.hearing_aid_priority;
+ return profileConnectionPolicies.hearing_aid_connection_policy;
}
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
void setCustomizedMeta(int key, byte[] value) {
@@ -305,8 +318,8 @@
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(address)
- .append(" {profile priority(")
- .append(profilePriorities)
+ .append(" {profile connection policy(")
+ .append(profileConnectionPolicies)
.append("), optional codec(support=")
.append(a2dpSupportsOptionalCodecs)
.append("|enabled=")
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDao.java b/src/com/android/bluetooth/btservice/storage/MetadataDao.java
index 7c5f440..5efdb24 100644
--- a/src/com/android/bluetooth/btservice/storage/MetadataDao.java
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDao.java
@@ -28,7 +28,7 @@
/**
* Load all items in the database
*/
- @Query("SELECT * FROM metadata")
+ @Query("SELECT * FROM metadata ORDER BY last_active_time DESC")
List<Metadata> load();
/**
diff --git a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
index fe56e3a..888f0b0 100644
--- a/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
+++ b/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -17,6 +17,8 @@
package com.android.bluetooth.btservice.storage;
import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
import androidx.room.Database;
import androidx.room.Room;
@@ -31,13 +33,15 @@
/**
* MetadataDatabase is a Room database stores Bluetooth persistence data
*/
-@Database(entities = {Metadata.class}, version = 102)
+@Database(entities = {Metadata.class}, version = 104)
public abstract class MetadataDatabase extends RoomDatabase {
/**
- * The database file name
+ * The metadata database file name
*/
public static final String DATABASE_NAME = "bluetooth_db";
+ static int sCurrentConnectionNumber = 0;
+
protected abstract MetadataDao mMetadataDao();
/**
@@ -51,6 +55,9 @@
MetadataDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_100_101)
.addMigrations(MIGRATION_101_102)
+ .addMigrations(MIGRATION_102_103)
+ .addMigrations(MIGRATION_103_104)
+ .allowMainThreadQueries()
.build();
}
@@ -65,11 +72,12 @@
return Room.databaseBuilder(context,
MetadataDatabase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()
+ .allowMainThreadQueries()
.build();
}
/**
- * Insert a {@link Metadata} to database
+ * Insert a {@link Metadata} to metadata table
*
* @param metadata the data wish to put into storage
*/
@@ -78,7 +86,7 @@
}
/**
- * Load all data from database as a {@link List} of {@link Metadata}
+ * Load all data from metadata table as a {@link List} of {@link Metadata}
*
* @return a {@link List} of {@link Metadata}
*/
@@ -87,7 +95,7 @@
}
/**
- * Delete one of the {@link Metadata} contains in database
+ * Delete one of the {@link Metadata} contained in the metadata table
*
* @param address the address of Metadata to delete
*/
@@ -96,7 +104,7 @@
}
/**
- * Clear database.
+ * Clear metadata table.
*/
public void deleteAll() {
mMetadataDao().deleteAll();
@@ -172,4 +180,127 @@
database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`");
}
};
+
+ @VisibleForTesting
+ static final Migration MIGRATION_102_103 = new Migration(102, 103) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ try {
+ database.execSQL("CREATE TABLE IF NOT EXISTS `metadata_tmp` ("
+ + "`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, "
+ + "`a2dpSupportsOptionalCodecs` INTEGER NOT NULL, "
+ + "`a2dpOptionalCodecsEnabled` INTEGER NOT NULL, "
+ + "`a2dp_connection_policy` INTEGER, "
+ + "`a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, "
+ + "`hfp_client_connection_policy` INTEGER, "
+ + "`hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, "
+ + "`pbap_connection_policy` INTEGER, "
+ + "`pbap_client_connection_policy` INTEGER, "
+ + "`map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, "
+ + "`hearing_aid_connection_policy` INTEGER, "
+ + "`map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, "
+ + "`model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, "
+ + "`companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, "
+ + "`untethered_left_icon` BLOB, `untethered_right_icon` BLOB, "
+ + "`untethered_case_icon` BLOB, `untethered_left_battery` BLOB, "
+ + "`untethered_right_battery` BLOB, `untethered_case_battery` BLOB, "
+ + "`untethered_left_charging` BLOB, `untethered_right_charging` BLOB, "
+ + "`untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, "
+ + "PRIMARY KEY(`address`))");
+
+ database.execSQL("INSERT INTO metadata_tmp ("
+ + "address, migrated, a2dpSupportsOptionalCodecs, "
+ + "a2dpOptionalCodecsEnabled, a2dp_connection_policy, "
+ + "a2dp_sink_connection_policy, hfp_connection_policy,"
+ + "hfp_client_connection_policy, hid_host_connection_policy,"
+ + "pan_connection_policy, pbap_connection_policy,"
+ + "pbap_client_connection_policy, map_connection_policy, "
+ + "sap_connection_policy, hearing_aid_connection_policy, "
+ + "map_client_connection_policy, manufacturer_name, model_name, "
+ + "software_version, hardware_version, companion_app, main_icon, "
+ + "is_untethered_headset, untethered_left_icon, untethered_right_icon, "
+ + "untethered_case_icon, untethered_left_battery, "
+ + "untethered_right_battery, untethered_case_battery, "
+ + "untethered_left_charging, untethered_right_charging, "
+ + "untethered_case_charging, enhanced_settings_ui_uri) "
+ + "SELECT "
+ + "address, migrated, a2dpSupportsOptionalCodecs, "
+ + "a2dpOptionalCodecsEnabled, a2dp_priority, a2dp_sink_priority, "
+ + "hfp_priority, hfp_client_priority, hid_host_priority, pan_priority, "
+ + "pbap_priority, pbap_client_priority, map_priority, sap_priority, "
+ + "hearing_aid_priority, map_client_priority, "
+ + "CAST (manufacturer_name AS BLOB), "
+ + "CAST (model_name AS BLOB), "
+ + "CAST (software_version AS BLOB), "
+ + "CAST (hardware_version AS BLOB), "
+ + "CAST (companion_app AS BLOB), "
+ + "CAST (main_icon AS BLOB), "
+ + "CAST (is_untethered_headset AS BLOB), "
+ + "CAST (untethered_left_icon AS BLOB), "
+ + "CAST (untethered_right_icon AS BLOB), "
+ + "CAST (untethered_case_icon AS BLOB), "
+ + "CAST (untethered_left_battery AS BLOB), "
+ + "CAST (untethered_right_battery AS BLOB), "
+ + "CAST (untethered_case_battery AS BLOB), "
+ + "CAST (untethered_left_charging AS BLOB), "
+ + "CAST (untethered_right_charging AS BLOB), "
+ + "CAST (untethered_case_charging AS BLOB), "
+ + "CAST (enhanced_settings_ui_uri AS BLOB)"
+ + "FROM metadata");
+
+ database.execSQL("UPDATE metadata_tmp SET a2dp_connection_policy = 100 "
+ + "WHERE a2dp_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET a2dp_sink_connection_policy = 100 "
+ + "WHERE a2dp_sink_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET hfp_connection_policy = 100 "
+ + "WHERE hfp_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET hfp_client_connection_policy = 100 "
+ + "WHERE hfp_client_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET hid_host_connection_policy = 100 "
+ + "WHERE hid_host_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET pan_connection_policy = 100 "
+ + "WHERE pan_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET pbap_connection_policy = 100 "
+ + "WHERE pbap_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET pbap_client_connection_policy = 100 "
+ + "WHERE pbap_client_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET map_connection_policy = 100 "
+ + "WHERE map_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET sap_connection_policy = 100 "
+ + "WHERE sap_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET hearing_aid_connection_policy = 100 "
+ + "WHERE hearing_aid_connection_policy = 1000");
+ database.execSQL("UPDATE metadata_tmp SET map_client_connection_policy = 100 "
+ + "WHERE map_client_connection_policy = 1000");
+
+ database.execSQL("DROP TABLE `metadata`");
+ database.execSQL("ALTER TABLE `metadata_tmp` RENAME TO `metadata`");
+ } catch (SQLException ex) {
+ // Check if user has new schema, but is just missing the version update
+ Cursor cursor = database.query("SELECT * FROM metadata");
+ if (cursor == null || cursor.getColumnIndex("a2dp_connection_policy") == -1) {
+ throw ex;
+ }
+ }
+ }
+ };
+
+ @VisibleForTesting
+ static final Migration MIGRATION_103_104 = new Migration(103, 104) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ try {
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `last_active_time` "
+ + "INTEGER NOT NULL DEFAULT -1");
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `is_active_a2dp_device` "
+ + "INTEGER NOT NULL DEFAULT 0");
+ } catch (SQLException ex) {
+ // Check if user has new schema, but is just missing the version update
+ Cursor cursor = database.query("SELECT * FROM metadata");
+ if (cursor == null || cursor.getColumnIndex("last_active_time") == -1) {
+ throw ex;
+ }
+ }
+ }
+ };
}
diff --git a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
index d87fbde..480a859 100644
--- a/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
+++ b/src/com/android/bluetooth/btservice/storage/ProfilePrioritiesEntity.java
@@ -23,48 +23,48 @@
@Entity
class ProfilePrioritiesEntity {
/* Bluetooth profile priorities*/
- public int a2dp_priority;
- public int a2dp_sink_priority;
- public int hfp_priority;
- public int hfp_client_priority;
- public int hid_host_priority;
- public int pan_priority;
- public int pbap_priority;
- public int pbap_client_priority;
- public int map_priority;
- public int sap_priority;
- public int hearing_aid_priority;
- public int map_client_priority;
+ public int a2dp_connection_policy;
+ public int a2dp_sink_connection_policy;
+ public int hfp_connection_policy;
+ public int hfp_client_connection_policy;
+ public int hid_host_connection_policy;
+ public int pan_connection_policy;
+ public int pbap_connection_policy;
+ public int pbap_client_connection_policy;
+ public int map_connection_policy;
+ public int sap_connection_policy;
+ public int hearing_aid_connection_policy;
+ public int map_client_connection_policy;
ProfilePrioritiesEntity() {
- a2dp_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- a2dp_sink_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- hfp_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- hfp_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- hid_host_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- pan_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- pbap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- pbap_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- map_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- sap_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- hearing_aid_priority = BluetoothProfile.PRIORITY_UNDEFINED;
- map_client_priority = BluetoothProfile.PRIORITY_UNDEFINED;
+ a2dp_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ a2dp_sink_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ hfp_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ hfp_client_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ hid_host_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ pan_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ pbap_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ pbap_client_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ map_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ sap_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ hearing_aid_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+ map_client_connection_policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append("A2DP=").append(a2dp_priority)
- .append("|A2DP_SINK=").append(a2dp_sink_priority)
- .append("|HEADSET=").append(hfp_priority)
- .append("|HEADSET_CLIENT=").append(hfp_client_priority)
- .append("|HID_HOST=").append(hid_host_priority)
- .append("|PAN=").append(pan_priority)
- .append("|PBAP=").append(pbap_priority)
- .append("|PBAP_CLIENT=").append(pbap_client_priority)
- .append("|MAP=").append(map_priority)
- .append("|MAP_CLIENT=").append(map_client_priority)
- .append("|SAP=").append(sap_priority)
- .append("|HEARING_AID=").append(hearing_aid_priority);
+ builder.append("A2DP=").append(a2dp_connection_policy)
+ .append("|A2DP_SINK=").append(a2dp_sink_connection_policy)
+ .append("|HEADSET=").append(hfp_connection_policy)
+ .append("|HEADSET_CLIENT=").append(hfp_client_connection_policy)
+ .append("|HID_HOST=").append(hid_host_connection_policy)
+ .append("|PAN=").append(pan_connection_policy)
+ .append("|PBAP=").append(pbap_connection_policy)
+ .append("|PBAP_CLIENT=").append(pbap_client_connection_policy)
+ .append("|MAP=").append(map_connection_policy)
+ .append("|MAP_CLIENT=").append(map_client_connection_policy)
+ .append("|SAP=").append(sap_connection_policy)
+ .append("|HEARING_AID=").append(hearing_aid_connection_policy);
return builder.toString();
}
diff --git a/src/com/android/bluetooth/gatt/AppScanStats.java b/src/com/android/bluetooth/gatt/AppScanStats.java
index a895ece..4cab832 100644
--- a/src/com/android/bluetooth/gatt/AppScanStats.java
+++ b/src/com/android/bluetooth/gatt/AppScanStats.java
@@ -22,9 +22,9 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.WorkSource;
-import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.internal.app.IBatteryStats;
import java.text.DateFormat;
@@ -111,7 +111,7 @@
static final int SCAN_TIMEOUT_MS = 30 * 60 * 1000;
public String appName;
- public WorkSource mWorkSource; // Used for BatteryStats and StatsLog
+ public WorkSource mWorkSource; // Used for BatteryStats and BluetoothStatsLog
private int mScansStarted = 0;
private int mScansStopped = 0;
public boolean isRegistered = false;
@@ -159,7 +159,8 @@
} catch (RemoteException e) {
/* ignore */
}
- StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, 100);
+ BluetoothStatsLog.write(
+ BluetoothStatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, 100);
}
}
@@ -233,8 +234,8 @@
} catch (RemoteException e) {
/* ignore */
}
- StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
- StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
+ BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON,
scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan);
mOngoingScans.put(scannerId, scan);
@@ -297,9 +298,10 @@
} catch (RemoteException e) {
/* ignore */
}
- StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, scan.results % 100);
- StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
- StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF,
+ BluetoothStatsLog.write(
+ BluetoothStatsLog.BLE_SCAN_RESULT_RECEIVED, mWorkSource, scan.results % 100);
+ BluetoothStatsLog.write(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, mWorkSource,
+ BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF,
scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan);
}
diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java
index 8d372e7..0b44cde 100644
--- a/src/com/android/bluetooth/gatt/GattService.java
+++ b/src/com/android/bluetooth/gatt/GattService.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.Service;
@@ -466,22 +467,25 @@
@Override
public void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
- List storages, String callingPackage) {
+ List storages, String callingPackage, String callingFeatureId) {
GattService service = getService();
if (service == null) {
return;
}
- service.startScan(scannerId, settings, filters, storages, callingPackage);
+ service.startScan(scannerId, settings, filters, storages, callingPackage,
+ callingFeatureId);
}
@Override
public void startScanForIntent(PendingIntent intent, ScanSettings settings,
- List<ScanFilter> filters, String callingPackage) throws RemoteException {
+ List<ScanFilter> filters, String callingPackage, String callingFeatureId)
+ throws RemoteException {
GattService service = getService();
if (service == null) {
return;
}
- service.registerPiAndStartScan(intent, settings, filters, callingPackage);
+ service.registerPiAndStartScan(intent, settings, filters, callingPackage,
+ callingFeatureId);
}
@Override
@@ -1933,7 +1937,8 @@
}
void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
- List<List<ResultStorageDescriptor>> storages, String callingPackage) {
+ List<List<ResultStorageDescriptor>> storages, String callingPackage,
+ @Nullable String callingFeatureId) {
if (DBG) {
Log.d(TAG, "start scan with filters");
}
@@ -1947,13 +1952,11 @@
mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
scanClient.isQApp = Utils.isQApp(this, callingPackage);
if (scanClient.isQApp) {
- scanClient.hasLocationPermission =
- Utils.checkCallerHasFineLocation(
- this, mAppOps, callingPackage, scanClient.userHandle);
+ scanClient.hasLocationPermission = Utils.checkCallerHasFineLocation(this, mAppOps,
+ callingPackage, callingFeatureId, scanClient.userHandle);
} else {
- scanClient.hasLocationPermission =
- Utils.checkCallerHasCoarseOrFineLocation(
- this, mAppOps, callingPackage, scanClient.userHandle);
+ scanClient.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(this,
+ mAppOps, callingPackage, callingFeatureId, scanClient.userHandle);
}
scanClient.hasNetworkSettingsPermission =
Utils.checkCallerHasNetworkSettingsPermission(this);
@@ -1976,7 +1979,7 @@
}
void registerPiAndStartScan(PendingIntent pendingIntent, ScanSettings settings,
- List<ScanFilter> filters, String callingPackage) {
+ List<ScanFilter> filters, String callingPackage, @Nullable String callingFeatureId) {
if (DBG) {
Log.d(TAG, "start scan with filters, for PendingIntent");
}
@@ -2008,10 +2011,10 @@
try {
if (app.mIsQApp) {
app.hasLocationPermission = Utils.checkCallerHasFineLocation(
- this, mAppOps, callingPackage, app.mUserHandle);
+ this, mAppOps, callingPackage, callingFeatureId, app.mUserHandle);
} else {
app.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
- this, mAppOps, callingPackage, app.mUserHandle);
+ this, mAppOps, callingPackage, callingFeatureId, app.mUserHandle);
}
} catch (SecurityException se) {
// No need to throw here. Just mark as not granted.
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
index b32d304..1db0be6 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidNativeInterface.java
@@ -48,7 +48,7 @@
private HearingAidNativeInterface() {
mAdapter = BluetoothAdapter.getDefaultAdapter();
if (mAdapter == null) {
- Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+ Log.wtf(TAG, "No Bluetooth Adapter Available");
}
}
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index 3647a65..5d013dd 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -29,15 +29,16 @@
import android.os.HandlerThread;
import android.os.ParcelUuid;
import android.util.Log;
-import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.HashMap;
@@ -235,11 +236,11 @@
return false;
}
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
+ if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return false;
}
ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
- if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.HearingAid)) {
+ if (!ArrayUtils.contains(featureUuids, BluetoothUuid.HEARING_AID)) {
Log.e(TAG, "Cannot connect to " + device + " : Remote does not have Hearing Aid UUID");
return false;
}
@@ -366,19 +367,18 @@
Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
return false;
}
- // Check priority and accept or reject the connection.
- int priority = getPriority(device);
+ // Check connection policy and accept or reject the connection.
+ int connectionPolicy = getConnectionPolicy(device);
int bondState = mAdapterService.getBondState(device);
// Allow this connection only if the device is bonded. Any attempt to connect while
// bonding would potentially lead to an unauthorized connection.
if (bondState != BluetoothDevice.BOND_BONDED) {
Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
return false;
- } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
- && priority != BluetoothProfile.PRIORITY_ON
- && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
- // Otherwise, reject the connection if priority is not valid.
- Log.w(TAG, "okToConnect: return false, priority=" + priority);
+ } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ // Otherwise, reject the connection if connectionPolicy is not valid.
+ Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
return false;
}
return true;
@@ -397,7 +397,7 @@
synchronized (mStateMachines) {
for (BluetoothDevice device : bondedDevices) {
final ParcelUuid[] featureUuids = device.getUuids();
- if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.HearingAid)) {
+ if (!ArrayUtils.contains(featureUuids, BluetoothUuid.HEARING_AID)) {
continue;
}
int connectionState = BluetoothProfile.STATE_DISCONNECTED;
@@ -454,26 +454,51 @@
}
/**
- * Set the priority of the Hearing Aid profile.
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
*
- * @param device the remote device
- * @param priority the priority of the profile
- * @return true on success, otherwise false
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
*/
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
+ Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
mAdapterService.getDatabase()
- .setProfilePriority(device, BluetoothProfile.HEARING_AID, priority);
+ .setProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID, connectionPolicy);
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
return true;
}
- public int getPriority(BluetoothDevice device) {
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
return mAdapterService.getDatabase()
- .getProfilePriority(device, BluetoothProfile.HEARING_AID);
+ .getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID);
}
void setVolume(int volume) {
@@ -633,8 +658,8 @@
Log.d(TAG, "reportActiveDevice(" + device + ")");
}
- StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.HEARING_AID,
- mAdapterService.obfuscateAddress(device));
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED,
+ BluetoothProfile.HEARING_AID, mAdapterService.obfuscateAddress(device));
Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
@@ -884,21 +909,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
HearingAidService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
HearingAidService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
@Override
@@ -911,17 +936,6 @@
}
@Override
- public void adjustVolume(int direction) {
- // TODO: Remove me?
- }
-
- @Override
- public int getVolume() {
- // TODO: Remove me?
- return 0;
- }
-
- @Override
public long getHiSyncId(BluetoothDevice device) {
HearingAidService service = getService();
if (service == null) {
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
index 5b8d798..2d41399 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidStateMachine.java
@@ -54,9 +54,9 @@
import android.util.Log;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -178,7 +178,7 @@
Log.d(TAG, "Disconnected: stack event: " + event);
}
if (!mDevice.equals(event.device)) {
- Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+ Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -282,7 +282,7 @@
HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
log("Connecting: stack event: " + event);
if (!mDevice.equals(event.device)) {
- Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+ Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -367,7 +367,7 @@
HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
log("Disconnecting: stack event: " + event);
if (!mDevice.equals(event.device)) {
- Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+ Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
@@ -460,7 +460,7 @@
HearingAidStackEvent event = (HearingAidStackEvent) message.obj;
log("Connected: stack event: " + event);
if (!mDevice.equals(event.device)) {
- Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event);
+ Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event);
}
switch (event.type) {
case HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED:
diff --git a/src/com/android/bluetooth/hfp/AtPhonebook.java b/src/com/android/bluetooth/hfp/AtPhonebook.java
index 937e767..d4adfb1 100644
--- a/src/com/android/bluetooth/hfp/AtPhonebook.java
+++ b/src/com/android/bluetooth/hfp/AtPhonebook.java
@@ -31,7 +31,7 @@
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.util.DevicePolicyUtils;
-import com.android.internal.telephony.GsmAlphabet;
+import com.android.bluetooth.util.GsmAlphabet;
import java.util.HashMap;
diff --git a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
index 61c1163..29c571d 100644
--- a/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
@@ -63,7 +63,7 @@
} else {
// Service must call cleanup() when quiting and native stack shouldn't send any event
// after cleanup() -> cleanupNative() is called.
- Log.wtfStack(TAG, "FATAL: Stack sent event while service is not available: " + event);
+ Log.wtf(TAG, "FATAL: Stack sent event while service is not available: " + event);
}
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index 941ec14..d1dbe32 100644
--- a/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -17,11 +17,8 @@
package com.android.bluetooth.hfp;
import android.bluetooth.BluetoothDevice;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Looper;
+import android.os.Handler;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -31,11 +28,10 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.TelephonyIntents;
import java.util.HashMap;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
@@ -52,14 +48,12 @@
private final HeadsetService mHeadsetService;
private final TelephonyManager mTelephonyManager;
private final SubscriptionManager mSubscriptionManager;
+ private final Handler mHandler;
private ServiceState mServiceState;
// HFP 1.6 CIND service value
private int mCindService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
- // Check this before sending out service state to the device -- if the SIM isn't fully
- // loaded, don't expose that the network is available.
- private boolean mIsSimStateLoaded;
// Number of active (foreground) calls
private int mNumActive;
// Current Call Setup State
@@ -88,9 +82,10 @@
mSubscriptionManager = SubscriptionManager.from(mHeadsetService);
Objects.requireNonNull(mSubscriptionManager, "TELEPHONY_SUBSCRIPTION_SERVICE is null");
// Initialize subscription on the handler thread
- mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener(
- headsetService.getStateMachinesThreadLooper());
- mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+ mHandler = new Handler(headsetService.getStateMachinesThreadLooper());
+ mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener();
+ mSubscriptionManager.addOnSubscriptionsChangedListener(command -> mHandler.post(command),
+ mOnSubscriptionsChangedListener);
}
/**
@@ -160,14 +155,8 @@
return;
}
Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
- mPhoneStateListener = new HeadsetPhoneStateListener(
- mHeadsetService.getStateMachinesThreadLooper());
+ mPhoneStateListener = new HeadsetPhoneStateListener(command -> mHandler.post(command));
mTelephonyManager.listen(mPhoneStateListener, events);
- if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
- mTelephonyManager.setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
- }
}
private void stopListenForPhoneState() {
@@ -178,9 +167,6 @@
Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events="
+ getTelephonyEventsToListen());
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- mTelephonyManager.setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
mPhoneStateListener = null;
}
@@ -215,6 +201,10 @@
mNumHeld = numHeldCall;
}
+ ServiceState getServiceState() {
+ return mServiceState;
+ }
+
int getCindSignal() {
return mCindSignal;
}
@@ -244,29 +234,34 @@
return (mNumActive >= 1);
}
- private void sendDeviceStateChanged() {
- int service =
- mIsSimStateLoaded ? mCindService : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
+ private synchronized void sendDeviceStateChanged() {
// When out of service, send signal strength as 0. Some devices don't
// use the service indicator, but only the signal indicator
- int signal = service == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
+ int signal = mCindService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
- Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService + " mIsSimStateLoaded="
- + mIsSimStateLoaded + " mSignal=" + signal + " mRoam=" + mCindRoam
+ Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService
+ + " mSignal=" + mCindSignal + " mRoam=" + mCindRoam
+ " mBatteryCharge=" + mCindBatteryCharge);
mHeadsetService.onDeviceStateChanged(
- new HeadsetDeviceState(service, mCindRoam, signal, mCindBatteryCharge));
+ new HeadsetDeviceState(mCindService, mCindRoam, signal, mCindBatteryCharge));
}
private class HeadsetPhoneStateOnSubscriptionChangedListener
extends OnSubscriptionsChangedListener {
- HeadsetPhoneStateOnSubscriptionChangedListener(Looper looper) {
- super(looper);
+ HeadsetPhoneStateOnSubscriptionChangedListener() {
+ super();
}
@Override
public void onSubscriptionsChanged() {
synchronized (mDeviceEventMap) {
+ int simState = mTelephonyManager.getSimState();
+ if (simState != TelephonyManager.SIM_STATE_READY) {
+ mServiceState = null;
+ mCindSignal = 0;
+ mCindService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
+ sendDeviceStateChanged();
+ }
stopListenForPhoneState();
startListenForPhoneState();
}
@@ -274,8 +269,8 @@
}
private class HeadsetPhoneStateListener extends PhoneStateListener {
- HeadsetPhoneStateListener(Looper looper) {
- super(looper);
+ HeadsetPhoneStateListener(Executor executor) {
+ super(executor);
}
@Override
@@ -293,32 +288,7 @@
}
mCindService = cindService;
mCindRoam = newRoam;
-
- // If this is due to a SIM insertion, we want to defer sending device state changed
- // until all the SIM config is loaded.
- if (cindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
- mIsSimStateLoaded = false;
- sendDeviceStateChanged();
- return;
- }
- IntentFilter simStateChangedFilter =
- new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- mHeadsetService.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
- // This is a sticky broadcast, so if it's already been loaded,
- // this'll execute immediately.
- if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(
- intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
- mIsSimStateLoaded = true;
- sendDeviceStateChanged();
- mHeadsetService.unregisterReceiver(this);
- }
- }
- }
- }, simStateChangedFilter);
-
+ sendDeviceStateChanged();
}
@Override
diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java
index 8f9adae..d60085a 100644
--- a/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -31,19 +31,17 @@
import android.media.AudioManager;
import android.net.Uri;
import android.os.BatteryManager;
+import android.os.Handler;
import android.os.HandlerThread;
-import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.ParcelUuid;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.telecom.PhoneAccount;
import android.util.Log;
-import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
@@ -89,7 +87,7 @@
private static final boolean DBG = false;
private static final String DISABLE_INBAND_RINGING_PROPERTY =
"persist.bluetooth.disableinbandringing";
- private static final ParcelUuid[] HEADSET_UUIDS = {BluetoothUuid.HSP, BluetoothUuid.Handsfree};
+ private static final ParcelUuid[] HEADSET_UUIDS = {BluetoothUuid.HSP, BluetoothUuid.HFP};
private static final int[] CONNECTING_CONNECTED_STATES =
{BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED};
private static final int DIALING_OUT_TIMEOUT_MS = 10000;
@@ -98,6 +96,7 @@
private BluetoothDevice mActiveDevice;
private AdapterService mAdapterService;
private HandlerThread mStateMachinesThread;
+ private Handler mStateMachinesThreadHandler;
// This is also used as a lock for shared data in HeadsetService
private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>();
private HeadsetNativeInterface mNativeInterface;
@@ -195,11 +194,12 @@
mVoiceRecognitionStarted = false;
mVirtualCallStarted = false;
if (mDialingOutTimeoutEvent != null) {
- mStateMachinesThread.getThreadHandler().removeCallbacks(mDialingOutTimeoutEvent);
+ getStateMachinesThreadHandler()
+ .removeCallbacks(mDialingOutTimeoutEvent);
mDialingOutTimeoutEvent = null;
}
if (mVoiceRecognitionTimeoutEvent != null) {
- mStateMachinesThread.getThreadHandler()
+ getStateMachinesThreadHandler()
.removeCallbacks(mVoiceRecognitionTimeoutEvent);
mVoiceRecognitionTimeoutEvent = null;
if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
@@ -219,6 +219,7 @@
// Step 2: Stop handler thread
mStateMachinesThread.quitSafely();
mStateMachinesThread = null;
+ mStateMachinesThreadHandler = null;
// Step 1: Clear
synchronized (mStateMachines) {
mAdapterService = null;
@@ -359,7 +360,7 @@
synchronized (mStateMachines) {
final HeadsetStateMachine stateMachine = mStateMachines.get(device);
if (stateMachine == null) {
- Log.wtfStack(TAG, "Cannot find state machine for " + device);
+ Log.wtf(TAG, "Cannot find state machine for " + device);
return;
}
stateMachine.sendMessage(
@@ -476,21 +477,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
HeadsetService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
HeadsetService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
@Override
@@ -680,8 +681,9 @@
public boolean connect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- Log.w(TAG, "connect: PRIORITY_OFF, device=" + device + ", " + Utils.getUidPidString());
+ if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ Log.w(TAG, "connect: CONNECTION_POLICY_FORBIDDEN, device=" + device + ", "
+ + Utils.getUidPidString());
return false;
}
ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
@@ -815,19 +817,51 @@
}
}
- public boolean setPriority(BluetoothDevice device, int priority) {
+ /**
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
- Log.i(TAG, "setPriority: device=" + device + ", priority=" + priority + ", "
- + Utils.getUidPidString());
+ Log.i(TAG, "setConnectionPolicy: device=" + device
+ + ", connectionPolicy=" + connectionPolicy + ", " + Utils.getUidPidString());
mAdapterService.getDatabase()
- .setProfilePriority(device, BluetoothProfile.HEADSET, priority);
+ .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, connectionPolicy);
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
return true;
}
- public int getPriority(BluetoothDevice device) {
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mAdapterService.getDatabase()
- .getProfilePriority(device, BluetoothProfile.HEADSET);
+ .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET);
}
boolean startVoiceRecognition(BluetoothDevice device) {
@@ -871,8 +905,7 @@
+ ", fall back to requesting device");
device = mVoiceRecognitionTimeoutEvent.mVoiceRecognitionDevice;
}
- mStateMachinesThread.getThreadHandler()
- .removeCallbacks(mVoiceRecognitionTimeoutEvent);
+ getStateMachinesThreadHandler().removeCallbacks(mVoiceRecognitionTimeoutEvent);
mVoiceRecognitionTimeoutEvent = null;
if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
mSystemInterface.getVoiceRecognitionWakeLock().release();
@@ -1339,7 +1372,7 @@
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
mDialingOutTimeoutEvent = new DialingOutTimeoutEvent(fromDevice);
- mStateMachinesThread.getThreadHandler()
+ getStateMachinesThreadHandler()
.postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);
return true;
}
@@ -1424,28 +1457,14 @@
+ " as active");
return false;
}
- IDeviceIdleController deviceIdleController = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- if (deviceIdleController == null) {
- Log.w(TAG, "startVoiceRecognitionByHeadset: deviceIdleController is null, device="
- + fromDevice);
- return false;
- }
- try {
- deviceIdleController.exitIdle("voice-command");
- } catch (RemoteException e) {
- Log.w(TAG,
- "startVoiceRecognitionByHeadset: failed to exit idle, device=" + fromDevice
- + ", error=" + e.getMessage());
- return false;
- }
if (!mSystemInterface.activateVoiceRecognition()) {
Log.w(TAG, "startVoiceRecognitionByHeadset: failed request from " + fromDevice);
return false;
}
mVoiceRecognitionTimeoutEvent = new VoiceRecognitionTimeoutEvent(fromDevice);
- mStateMachinesThread.getThreadHandler()
+ getStateMachinesThreadHandler()
.postDelayed(mVoiceRecognitionTimeoutEvent, sStartVrTimeoutMs);
+
if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
mSystemInterface.getVoiceRecognitionWakeLock().acquire(sStartVrTimeoutMs);
}
@@ -1470,8 +1489,9 @@
if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
mSystemInterface.getVoiceRecognitionWakeLock().release();
}
- mStateMachinesThread.getThreadHandler()
+ getStateMachinesThreadHandler()
.removeCallbacks(mVoiceRecognitionTimeoutEvent);
+
mVoiceRecognitionTimeoutEvent = null;
}
if (mVoiceRecognitionStarted) {
@@ -1507,7 +1527,7 @@
if (mDialingOutTimeoutEvent != null) {
// Send result to state machine when dialing starts
if (callState == HeadsetHalConstants.CALL_STATE_DIALING) {
- mStateMachinesThread.getThreadHandler()
+ getStateMachinesThreadHandler()
.removeCallbacks(mDialingOutTimeoutEvent);
doForStateMachine(mDialingOutTimeoutEvent.mDialingOutDevice,
stateMachine -> stateMachine.sendMessage(
@@ -1516,14 +1536,14 @@
} else if (callState == HeadsetHalConstants.CALL_STATE_ACTIVE
|| callState == HeadsetHalConstants.CALL_STATE_IDLE) {
// Clear the timeout event when the call is connected or disconnected
- if (!mStateMachinesThread.getThreadHandler()
+ if (!getStateMachinesThreadHandler()
.hasCallbacks(mDialingOutTimeoutEvent)) {
mDialingOutTimeoutEvent = null;
}
}
}
}
- mStateMachinesThread.getThreadHandler().post(() -> {
+ getStateMachinesThreadHandler().post(() -> {
boolean isCallIdleBefore = mSystemInterface.isCallIdle();
mSystemInterface.getHeadsetPhoneState().setNumActiveCall(numActive);
mSystemInterface.getHeadsetPhoneState().setNumHeldCall(numHeld);
@@ -1537,7 +1557,7 @@
doForEachConnectedStateMachine(
stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED,
new HeadsetCallState(numActive, numHeld, callState, number, type, name)));
- mStateMachinesThread.getThreadHandler().post(() -> {
+ getStateMachinesThreadHandler().post(() -> {
if (callState == HeadsetHalConstants.CALL_STATE_IDLE
&& mSystemInterface.isCallIdle() && !isAudioOn()) {
// Resume A2DP when call ended and SCO is not connected
@@ -1706,8 +1726,8 @@
private void broadcastActiveDevice(BluetoothDevice device) {
logD("broadcastActiveDevice: " + device);
- StatsLog.write(StatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.HEADSET,
- mAdapterService.obfuscateAddress(device));
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED,
+ BluetoothProfile.HEADSET, mAdapterService.obfuscateAddress(device));
Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
@@ -1727,19 +1747,18 @@
Log.w(TAG, "okToAcceptConnection: return false as quiet mode enabled");
return false;
}
- // Check priority and accept or reject the connection.
- int priority = getPriority(device);
+ // Check connection policy and accept or reject the connection.
+ int connectionPolicy = getConnectionPolicy(device);
int bondState = mAdapterService.getBondState(device);
// Allow this connection only if the device is bonded. Any attempt to connect while
// bonding would potentially lead to an unauthorized connection.
if (bondState != BluetoothDevice.BOND_BONDED) {
Log.w(TAG, "okToAcceptConnection: return false, bondState=" + bondState);
return false;
- } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
- && priority != BluetoothProfile.PRIORITY_ON
- && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
- // Otherwise, reject the connection if priority is not valid.
- Log.w(TAG, "okToAcceptConnection: return false, priority=" + priority);
+ } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ // Otherwise, reject the connection if connection policy is not valid.
+ Log.w(TAG, "okToAcceptConnection: return false, connectionPolicy=" + connectionPolicy);
return false;
}
List<BluetoothDevice> connectingConnectedDevices =
@@ -1850,4 +1869,11 @@
Log.d(TAG, message);
}
}
+
+ private Handler getStateMachinesThreadHandler() {
+ if (mStateMachinesThreadHandler == null) {
+ mStateMachinesThreadHandler = new Handler(mStateMachinesThread.getLooper());
+ }
+ return mStateMachinesThreadHandler;
+ }
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 06c4822..8656383 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -31,15 +31,17 @@
import android.os.UserHandle;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
import android.text.TextUtils;
import android.util.Log;
-import android.util.StatsLog;
+import com.android.bluetooth.BluetoothStatsLog;
+import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -306,7 +308,7 @@
// Should not be called from enter() method
void broadcastAudioState(BluetoothDevice device, int fromState, int toState) {
stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState);
- StatsLog.write(StatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
mAdapterService.obfuscateAddress(device),
getConnectionStateFromAudioState(toState),
TextUtils.equals(mAudioParams.get(HEADSET_WBS), HEADSET_AUDIO_FEATURE_ON)
@@ -392,8 +394,8 @@
logi(getName() + ": currentDevice=" + mDevice + ", msg=" + msg);
}
- void stateLogWtfStack(String msg) {
- Log.wtfStack(TAG, getName() + ": " + msg);
+ void stateLogWtf(String msg) {
+ Log.wtf(TAG, getName() + ": " + msg);
}
/**
@@ -518,8 +520,9 @@
stateLogI("accept incoming connection");
transitionTo(mConnecting);
} else {
- stateLogI("rejected incoming HF, priority=" + mHeadsetService.getPriority(
- mDevice) + " bondState=" + mAdapterService.getBondState(mDevice));
+ stateLogI("rejected incoming HF, connectionPolicy="
+ + mHeadsetService.getConnectionPolicy(mDevice) + " bondState="
+ + mAdapterService.getBondState(mDevice));
// Reject the connection and stay in Disconnected state itself
if (!mNativeInterface.disconnectHfp(mDevice)) {
stateLogE("failed to disconnect");
@@ -841,8 +844,6 @@
break;
}
case CALL_STATE_CHANGED: {
- if (mDeviceSilenced) break;
-
HeadsetCallState callState = (HeadsetCallState) message.obj;
if (!mNativeInterface.phoneStateChange(mDevice, callState)) {
stateLogW("processCallState: failed to update call state " + callState);
@@ -1587,7 +1588,7 @@
if (number.charAt(number.length() - 1) == ';') {
number = number.substring(0, number.length() - 1);
}
- dialNumber = PhoneNumberUtils.convertPreDial(number);
+ dialNumber = Utils.convertPreDial(number);
}
if (!mHeadsetService.dialOutgoingCall(mDevice, dialNumber)) {
Log.w(TAG, "processDialCall, failed to dial in service");
@@ -1703,7 +1704,16 @@
}
private void processAtCops(BluetoothDevice device) {
- String operatorName = mSystemInterface.getNetworkOperator();
+ // Get operator name suggested by Telephony
+ String operatorName = null;
+ ServiceState serviceState = mSystemInterface.getHeadsetPhoneState().getServiceState();
+ if (serviceState != null) {
+ operatorName = serviceState.getOperatorAlpha();
+ }
+ if (mSystemInterface.isInCall() || operatorName == null || operatorName.equals("")) {
+ // Get operator name suggested by Telecom
+ operatorName = mSystemInterface.getNetworkOperator();
+ }
if (operatorName == null) {
operatorName = "";
}
@@ -1868,7 +1878,7 @@
String vendorId = deviceInfo[0];
String productId = deviceInfo[1];
String version = deviceInfo[2];
- StatsLog.write(StatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED,
mAdapterService.obfuscateAddress(device), BluetoothProtoEnums.DEVICE_INFO_INTERNAL,
BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XAPL, vendorId, productId, version,
null);
@@ -2022,7 +2032,7 @@
events |= PhoneStateListener.LISTEN_SERVICE_STATE;
}
if (mAgIndicatorEnableState != null && mAgIndicatorEnableState.signal) {
- events |= PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
+ events |= PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH;
}
mSystemInterface.getHeadsetPhoneState().listenForPhoneState(mDevice, events);
}
diff --git a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
index 81090eb..52ca1bc 100644
--- a/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
+++ b/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -16,6 +16,8 @@
package com.android.bluetooth.hfp;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.IBluetoothHeadsetPhone;
@@ -24,6 +26,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.os.IBinder;
import android.os.PowerManager;
@@ -32,6 +37,8 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.List;
+
/**
* Defines system calls that is used by state machine/service to either send or receive
* messages from the Android System.
@@ -70,7 +77,7 @@
HeadsetSystemInterface(HeadsetService headsetService) {
if (headsetService == null) {
- Log.wtfStack(TAG, "HeadsetService parameter is null");
+ Log.wtf(TAG, "HeadsetService parameter is null");
}
mHeadsetService = headsetService;
mAudioManager = (AudioManager) mHeadsetService.getSystemService(Context.AUDIO_SERVICE);
@@ -88,15 +95,48 @@
public synchronized void init() {
// Bind to Telecom phone proxy service
Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
- intent.setComponent(intent.resolveSystemService(mHeadsetService.getPackageManager(), 0));
+ intent.setComponent(resolveSystemService(mHeadsetService.getPackageManager(), 0, intent));
if (intent.getComponent() == null || !mHeadsetService.bindService(intent,
mPhoneProxyConnection, 0)) {
// Crash the stack if cannot bind to Telecom
- Log.wtfStack(TAG, "Could not bind to IBluetoothHeadsetPhone Service, intent=" + intent);
+ Log.wtf(TAG, "Could not bind to IBluetoothHeadsetPhone Service, intent=" + intent);
}
}
/**
+ * Special function for use by the system to resolve service
+ * intents to system apps. Throws an exception if there are
+ * multiple potential matches to the Intent. Returns null if
+ * there are no matches.
+ */
+ private @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
+ @PackageManager.ComponentInfoFlags int flags, Intent intent) {
+ if (intent.getComponent() != null) {
+ return intent.getComponent();
+ }
+
+ List<ResolveInfo> results = pm.queryIntentServices(intent, flags);
+ if (results == null) {
+ return null;
+ }
+ ComponentName comp = null;
+ for (int i = 0; i < results.size(); i++) {
+ ResolveInfo ri = results.get(i);
+ if ((ri.serviceInfo.applicationInfo.flags& ApplicationInfo.FLAG_SYSTEM) == 0) {
+ continue;
+ }
+ ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
+ ri.serviceInfo.name);
+ if (comp != null) {
+ throw new IllegalStateException("Multiple system services handle " + this
+ + ": " + comp + ", " + foundComp);
+ }
+ comp = foundComp;
+ }
+ return comp;
+ }
+
+ /**
* Stop this system interface
*/
public synchronized void stop() {
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index e7412fe..527a29e 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -257,21 +257,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
HeadsetClientService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
HeadsetClientService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
@Override
@@ -482,8 +482,9 @@
return false;
}
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is PRIORITY_OFF");
+ if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ Log.w(TAG, "Connection not allowed: <" + device.getAddress()
+ + "> is CONNECTION_POLICY_FORBIDDEN");
return false;
}
@@ -551,20 +552,52 @@
return BluetoothProfile.STATE_DISCONNECTED;
}
- public boolean setPriority(BluetoothDevice device, int priority) {
+ /**
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
+ Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
- AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.HEADSET_CLIENT, priority);
+ AdapterService.getAdapterService().getDatabase().setProfileConnectionPolicy(
+ device, BluetoothProfile.HEADSET_CLIENT, connectionPolicy);
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
return true;
}
- public int getPriority(BluetoothDevice device) {
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.HEADSET_CLIENT);
+ .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET_CLIENT);
}
boolean startVoiceRecognition(BluetoothDevice device) {
diff --git a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 3093aaf..3b241e3 100644
--- a/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -51,18 +51,19 @@
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
-import android.util.StatsLog;
import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.R;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.statemachine.IState;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IState;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
+import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -876,8 +877,9 @@
mCurrentDevice = device;
transitionTo(mConnecting);
} else {
- Log.i(TAG, "Incoming AG rejected. priority=" + mService.getPriority(device)
- + " bondState=" + device.getBondState());
+ Log.i(TAG, "Incoming AG rejected. connectionPolicy="
+ + mService.getConnectionPolicy(device) + " bondState="
+ + device.getBondState());
// reject the connection and stay in Disconnected state
// itself
mNativeInterface.disconnect(getByteAddress(device));
@@ -1664,7 +1666,7 @@
}
private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) {
- StatsLog.write(StatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
+ BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED,
AdapterService.getAdapterService().obfuscateAddress(device),
getConnectionStateFromAudioState(newState), mAudioWbs
? BluetoothHfpProtoEnums.SCO_CODEC_MSBC
@@ -1752,7 +1754,7 @@
synchronized (this) {
for (BluetoothDevice device : bondedDevices) {
ParcelUuid[] featureUuids = device.getUuids();
- if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.Handsfree_AG)) {
+ if (!ArrayUtils.contains(featureUuids, BluetoothUuid.HFP_AG)) {
continue;
}
connectionState = getConnectionState(device);
@@ -1767,16 +1769,16 @@
}
boolean okToConnect(BluetoothDevice device) {
- int priority = mService.getPriority(device);
+ int connectionPolicy = mService.getConnectionPolicy(device);
boolean ret = false;
- // check priority and accept or reject the connection. if priority is
+ // check connection policy and accept or reject the connection. if connection policy is
// undefined
// it is likely that our SDP has not completed and peer is initiating
// the
// connection. Allow this connection, provided the device is bonded
- if ((BluetoothProfile.PRIORITY_OFF < priority) || (
- (BluetoothProfile.PRIORITY_UNDEFINED == priority) && (device.getBondState()
- != BluetoothDevice.BOND_NONE))) {
+ if ((BluetoothProfile.CONNECTION_POLICY_FORBIDDEN < connectionPolicy) || (
+ (BluetoothProfile.CONNECTION_POLICY_UNKNOWN == connectionPolicy)
+ && (device.getBondState() != BluetoothDevice.BOND_NONE))) {
ret = true;
}
return ret;
diff --git a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
index e3c024a..aba50c0 100644
--- a/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
+++ b/src/com/android/bluetooth/hfpclient/connserv/HfpClientDeviceBlock.java
@@ -224,7 +224,7 @@
if (DBG) {
Log.d(mTAG, "prevConn " + prevConn.isClosing() + " new call " + newCall.getState());
}
- if (prevConn.isClosing()
+ if (prevConn.isClosing() && prevConn.getCall().getState() != newCall.getState()
&& newCall.getState() != BluetoothHeadsetClientCall.CALL_STATE_TERMINATED) {
return true;
}
diff --git a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
index db72a59..e99e448 100644
--- a/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
+++ b/src/com/android/bluetooth/hid/HidDeviceNativeInterface.java
@@ -49,7 +49,7 @@
private HidDeviceNativeInterface() {
mAdapter = BluetoothAdapter.getDefaultAdapter();
if (mAdapter == null) {
- Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+ Log.wtf(TAG, "No Bluetooth Adapter Available");
}
}
@@ -180,7 +180,7 @@
if (service != null) {
service.onApplicationStateChangedFromNative(getDevice(address), registered);
} else {
- Log.wtfStack(TAG, "FATAL: onApplicationStateChanged() "
+ Log.wtf(TAG, "FATAL: onApplicationStateChanged() "
+ "is called from the stack while service is not available.");
}
}
@@ -190,7 +190,7 @@
if (service != null) {
service.onConnectStateChangedFromNative(getDevice(address), state);
} else {
- Log.wtfStack(TAG, "FATAL: onConnectStateChanged() "
+ Log.wtf(TAG, "FATAL: onConnectStateChanged() "
+ "is called from the stack while service is not available.");
}
}
@@ -200,7 +200,7 @@
if (service != null) {
service.onGetReportFromNative(type, id, bufferSize);
} else {
- Log.wtfStack(TAG, "FATAL: onGetReport() "
+ Log.wtf(TAG, "FATAL: onGetReport() "
+ "is called from the stack while service is not available.");
}
}
@@ -210,7 +210,7 @@
if (service != null) {
service.onSetReportFromNative(reportType, reportId, data);
} else {
- Log.wtfStack(TAG, "FATAL: onSetReport() "
+ Log.wtf(TAG, "FATAL: onSetReport() "
+ "is called from the stack while service is not available.");
}
}
@@ -220,7 +220,7 @@
if (service != null) {
service.onSetProtocolFromNative(protocol);
} else {
- Log.wtfStack(TAG, "FATAL: onSetProtocol() "
+ Log.wtf(TAG, "FATAL: onSetProtocol() "
+ "is called from the stack while service is not available.");
}
}
@@ -230,7 +230,7 @@
if (service != null) {
service.onInterruptDataFromNative(reportId, data);
} else {
- Log.wtfStack(TAG, "FATAL: onInterruptData() "
+ Log.wtf(TAG, "FATAL: onInterruptData() "
+ "is called from the stack while service is not available.");
}
}
@@ -240,7 +240,7 @@
if (service != null) {
service.onVirtualCableUnplugFromNative();
} else {
- Log.wtfStack(TAG, "FATAL: onVirtualCableUnplug() "
+ Log.wtf(TAG, "FATAL: onVirtualCableUnplug() "
+ "is called from the stack while service is not available.");
}
}
diff --git a/src/com/android/bluetooth/hid/HidDeviceService.java b/src/com/android/bluetooth/hid/HidDeviceService.java
index b2d7e41..d0a1970 100644
--- a/src/com/android/bluetooth/hid/HidDeviceService.java
+++ b/src/com/android/bluetooth/hid/HidDeviceService.java
@@ -405,6 +405,21 @@
}
@Override
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ if (DBG) {
+ Log.d(TAG, "setConnectionPolicy(): device=" + device + " connectionPolicy="
+ + connectionPolicy);
+ }
+
+ HidDeviceService service = getService();
+ if (service == null) {
+ return false;
+ }
+
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+
+ @Override
public boolean reportError(BluetoothDevice device, byte error) {
if (DBG) {
Log.d(TAG, "reportError(): device=" + device + " error=" + error);
@@ -627,6 +642,35 @@
return checkDevice(device) && mHidDeviceNativeInterface.disconnect();
}
+ /**
+ * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
+ * and disconnects Hid device if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy determines whether hid device should be connected or disconnected
+ * @return true if hid device is connected or disconnected, false otherwise
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ enforceCallingOrSelfPermission(
+ BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
+ if (DBG) {
+ Log.d(TAG, "setConnectionPolicy: device " + device
+ + " and connectionPolicy " + connectionPolicy);
+ }
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ return true;
+ }
+ return false;
+ }
+
synchronized boolean reportError(BluetoothDevice device, byte error) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) {
diff --git a/src/com/android/bluetooth/hid/HidHostService.java b/src/com/android/bluetooth/hid/HidHostService.java
index 2e0019c..fa1ef59 100644
--- a/src/com/android/bluetooth/hid/HidHostService.java
+++ b/src/com/android/bluetooth/hid/HidHostService.java
@@ -373,21 +373,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
HidHostService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
HidHostService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
/* The following APIs regarding test app for compliance */
@@ -482,8 +482,8 @@
Log.e(TAG, "Hid Device not disconnected: " + device);
return false;
}
- if (getPriority(device) == BluetoothHidHost.PRIORITY_OFF) {
- Log.e(TAG, "Hid Device PRIORITY_OFF: " + device);
+ if (getConnectionPolicy(device) == BluetoothHidHost.CONNECTION_POLICY_FORBIDDEN) {
+ Log.e(TAG, "Hid Device CONNECTION_POLICY_FORBIDDEN: " + device);
return false;
}
@@ -531,26 +531,58 @@
return inputDevices;
}
- public boolean setPriority(BluetoothDevice device, int priority) {
+ /**
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
- Log.d(TAG, "setPriority: " + device.getAddress());
+ Log.d(TAG, "setConnectionPolicy: " + device.getAddress());
}
if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
+ Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.HID_HOST, priority);
+ .setProfileConnectionPolicy(device, BluetoothProfile.HID_HOST, connectionPolicy);
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
return true;
}
- public int getPriority(BluetoothDevice device) {
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
- Log.d(TAG, "getPriority: " + device.getAddress());
+ Log.d(TAG, "getConnectionPolicy: " + device.getAddress());
}
return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.HID_HOST);
+ .getProfileConnectionPolicy(device, BluetoothProfile.HID_HOST);
}
/* The following APIs regarding test app for compliance */
@@ -825,19 +857,18 @@
Log.w(TAG, "okToConnect: return false as quiet mode enabled");
return false;
}
- // Check priority and accept or reject the connection.
- int priority = getPriority(device);
+ // Check connection policy and accept or reject the connection.
+ int connectionPolicy = getConnectionPolicy(device);
int bondState = adapterService.getBondState(device);
// Allow this connection only if the device is bonded. Any attempt to connect while
// bonding would potentially lead to an unauthorized connection.
if (bondState != BluetoothDevice.BOND_BONDED) {
Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
return false;
- } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED
- && priority != BluetoothProfile.PRIORITY_ON
- && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
- // Otherwise, reject the connection if priority is not valid.
- Log.w(TAG, "okToConnect: return false, priority=" + priority);
+ } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ // Otherwise, reject the connection if connectionPolicy is not valid.
+ Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
return false;
}
return true;
diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
index c1c2846..e48b5e2 100644
--- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java
+++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java
@@ -63,16 +63,16 @@
private static final int PRESENCE_AVAILABLE = 0x1C;
private static final int PRESENCE_TEXT = 0x1D;
private static final int LAST_ACTIVITY = 0x1E;
- private static final int CHAT_STATE = 0x1F;
- private static final int FILTER_CONVO_ID = 0x20;
- private static final int CONVO_LISTING_SIZE = 0x21;
- private static final int FILTER_PRESENCE = 0x22;
- private static final int FILTER_UID_PRESENT = 0x23;
- private static final int CHAT_STATE_CONVO_ID = 0x24;
- private static final int FOLDER_VER_COUNTER = 0x25;
- private static final int FILTER_MESSAGE_HANDLE = 0x26;
- private static final int NOTIFICATION_FILTER = 0x27;
- private static final int CONVO_PARAMETER_MASK = 0x28;
+ private static final int CHAT_STATE = 0x21;
+ private static final int FILTER_CONVO_ID = 0x22;
+ private static final int CONVO_LISTING_SIZE = 0x36;
+ private static final int FILTER_PRESENCE = 0x37;
+ private static final int FILTER_UID_PRESENT = 0x38;
+ private static final int CHAT_STATE_CONVO_ID = 0x39;
+ private static final int FOLDER_VER_COUNTER = 0x23;
+ private static final int FILTER_MESSAGE_HANDLE = 0x24;
+ private static final int NOTIFICATION_FILTER = 0x25;
+ private static final int CONVO_PARAMETER_MASK = 0x26;
// Length defined for Application Parameters
private static final int MAX_LIST_COUNT_LEN = 0x02; //, 0x0000, 0xFFFF),
@@ -104,8 +104,8 @@
private static final int CONVO_LISTING_SIZE_LEN = 0x02;
private static final int FILTER_PRESENCE_LEN = 0x01;
private static final int FILTER_UID_PRESENT_LEN = 0x01;
- private static final int FOLDER_VER_COUNTER_LEN = 0x10;
- private static final int FILTER_MESSAGE_HANDLE_LEN = 0x10;
+ private static final int FOLDER_VER_COUNTER_LEN = 0x20;
+ private static final int FILTER_MESSAGE_HANDLE_LEN = 0x08;
private static final int NOTIFICATION_FILTER_LEN = 0x04;
private static final int CONVO_PARAMETER_MASK_LEN = 0x04;
@@ -1046,7 +1046,7 @@
public void setFilterMsgHandle(String handle) {
try {
mFilterMsgHandle = BluetoothMapUtils.getLongFromString(handle);
- } catch (UnsupportedEncodingException e) {
+ } catch (UnsupportedEncodingException | NumberFormatException e) {
Log.w(TAG, "Error creating long from handle string", e);
}
}
@@ -1100,7 +1100,7 @@
public void setFilterConvoId(String id) {
try {
mFilterConvoId = SignedLongLong.fromString(id);
- } catch (UnsupportedEncodingException e) {
+ } catch (UnsupportedEncodingException | NumberFormatException e) {
Log.w(TAG, "Error creating long from id string", e);
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java
index 2cda124..09894e8 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -86,13 +86,13 @@
private static final int MASK_REPLYTO_ADDRESSING = 0x00008000;
// TODO: Duplicate in proposed spec
// private static final int MASK_RECEPTION_STATE = 0x00010000;
- private static final int MASK_DELIVERY_STATUS = 0x00020000;
- private static final int MASK_CONVERSATION_ID = 0x00040000;
- private static final int MASK_CONVERSATION_NAME = 0x00080000;
+ private static final int MASK_DELIVERY_STATUS = 0x00010000;
+ private static final int MASK_CONVERSATION_ID = 0x00020000;
+ private static final int MASK_CONVERSATION_NAME = 0x00040000;
private static final int MASK_FOLDER_TYPE = 0x00100000;
// TODO: about to be removed from proposed spec
// private static final int MASK_SEQUENCE_NUMBER = 0x00200000;
- private static final int MASK_ATTACHMENT_MIME = 0x00400000;
+ private static final int MASK_ATTACHMENT_MIME = 0x00100000;
private static final int CONVO_PARAM_MASK_CONVO_NAME = 0x00000001;
private static final int CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY = 0x00000002;
@@ -2190,7 +2190,6 @@
if (tm != null) {
fi.mPhoneType = tm.getPhoneType();
fi.mPhoneNum = tm.getLine1Number();
- fi.mPhoneAlphaTag = tm.getLine1AlphaTag();
}
}
@@ -2454,7 +2453,6 @@
setDeliveryStatus(ele, tmpCursor, fi, ap);
setThreadId(ele, tmpCursor, fi, ap);
setThreadName(ele, tmpCursor, fi, ap);
- setFolderType(ele, tmpCursor, fi, ap);
}
}
}
@@ -3709,9 +3707,10 @@
if (charset == MAP_MESSAGE_CHARSET_NATIVE) {
if (type == 1) { //Inbox
message.setSmsBodyPdus(
- BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time));
+ BluetoothMapSmsPdu.getDeliverPdus(mContext, msgBody, phone, time));
} else {
- message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone));
+ message.setSmsBodyPdus(
+ BluetoothMapSmsPdu.getSubmitPdus(mContext, msgBody, phone));
}
} else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ {
message.setSmsBody(msgBody);
diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 8a73ef2..521b481 100644
--- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -47,12 +47,14 @@
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Xml;
+import com.android.bluetooth.Utils;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
import com.android.bluetooth.mapapi.BluetoothMapContract;
@@ -113,7 +115,7 @@
private static final long EVENT_FILTER_CONVERSATION_CHANGED = 1L << 10;
private static final long EVENT_FILTER_PARTICIPANT_PRESENCE_CHANGED = 1L << 11;
private static final long EVENT_FILTER_PARTICIPANT_CHATSTATE_CHANGED = 1L << 12;
- private static final long EVENT_FILTER_MESSAGE_REMOVED = 1L << 13;
+ private static final long EVENT_FILTER_MESSAGE_REMOVED = 1L << 14;
// TODO: If we are requesting a large message from the network, on a slow connection
// 20 seconds might not be enough... But then again 20 seconds is long for other
@@ -313,7 +315,7 @@
public void setObserverRemoteFeatureMask(int remoteSupportedFeatures) {
mMapSupportedFeatures =
- remoteSupportedFeatures & BluetoothMapMasInstance.SDP_MAP_MAS_FEATURES;
+ remoteSupportedFeatures & BluetoothMapMasInstance.getFeatureMask();
if ((BluetoothMapUtils.MAP_FEATURE_EXTENDED_EVENT_REPORT_11_BIT & mMapSupportedFeatures)
!= 0) {
mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
@@ -1452,10 +1454,7 @@
Context.TELEPHONY_SERVICE);
if (tm != null) {
phone = tm.getLine1Number();
- name = tm.getLine1AlphaTag();
- if (name == null || name.isEmpty()) {
- name = phone;
- }
+ name = phone;
}
}
String priority = "no"; // no priority for sms
@@ -1527,15 +1526,21 @@
}
} while (c.moveToNext());
}
+ } catch (IllegalStateException e) {
+ Log.w(TAG, e);
} finally {
if (c != null) {
c.close();
}
}
-
+ String eventType = EVENT_TYPE_DELETE;
for (Msg msg : getMsgListSms().values()) {
// "old_folder" used only for MessageShift event
- Event evt = new Event(EVENT_TYPE_DELETE, msg.id, getSmsFolderName(msg.type), null,
+ if (mMapEventReportVersion >= BluetoothMapUtils.MAP_EVENT_REPORT_V12) {
+ eventType = EVENT_TYPE_REMOVED;
+ if (V) Log.v(TAG," sent EVENT_TYPE_REMOVED");
+ }
+ Event evt = new Event(eventType, msg.id, getSmsFolderName(msg.type), null,
mSmsType);
sendEvent(evt);
listChanged = true;
@@ -1692,6 +1697,8 @@
} while (c.moveToNext());
}
+ } catch (IllegalStateException e) {
+ Log.w(TAG, e);
} finally {
if (c != null) {
c.close();
@@ -2544,6 +2551,37 @@
private Map<Long, PushMsgInfo> mPushMsgList =
Collections.synchronizedMap(new HashMap<Long, PushMsgInfo>());
+ /**
+ * Add an SMS to the given URI.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @return the URI for the new message
+ */
+ private static Uri addMessageToUri(ContentResolver resolver, Uri uri,
+ String address, String body, String subject,
+ Long date) {
+ ContentValues values = new ContentValues(7);
+ final int statusPending = 32;
+ final int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+ Log.v(TAG, "Telephony addMessageToUri sub id: " + subId);
+
+ values.put(Telephony.Sms.SUBSCRIPTION_ID, subId);
+ values.put(Telephony.Sms.ADDRESS, address);
+ if (date != null) {
+ values.put(Telephony.Sms.DATE, date);
+ }
+ values.put(Telephony.Sms.READ, 0);
+ values.put(Telephony.Sms.SUBJECT, subject);
+ values.put(Telephony.Sms.BODY, body);
+ values.put(Telephony.Sms.STATUS, statusPending);
+ return resolver.insert(uri, values);
+ }
+
public long pushMessage(BluetoothMapbMessage msg, BluetoothMapFolderElement folderElement,
BluetoothMapAppParams ap, String emailBaseUri)
throws IllegalArgumentException, RemoteException, IOException {
@@ -2649,8 +2687,6 @@
String phone = recipient.getFirstPhoneNumber();
String email = recipient.getFirstEmail();
String folder = folderElement.getName();
- boolean read = false;
- boolean deliveryReport = true;
String msgBody = null;
/* If MMS contains text only and the size is less than ten SMS's
@@ -2699,8 +2735,8 @@
Uri contentUri = Uri.parse(Sms.CONTENT_URI + "/" + folder);
Uri uri;
synchronized (getMsgListSms()) {
- uri = Sms.addMessageToUri(mResolver, contentUri, phone, msgBody, "",
- System.currentTimeMillis(), read, deliveryReport);
+ uri = addMessageToUri(mResolver, contentUri, phone, msgBody, "",
+ System.currentTimeMillis());
if (V) {
Log.v(TAG, "Sms.addMessageToUri() returned: " + uri);
@@ -3272,7 +3308,7 @@
Log.d(TAG, "actionMessageSent: result OK");
}
if (msgInfo.transparent == 0) {
- if (!Sms.moveMessageToFolder(context, msgInfo.uri, Sms.MESSAGE_TYPE_SENT, 0)) {
+ if (!Utils.moveMessageToFolder(context, msgInfo.uri, true)) {
Log.w(TAG, "Failed to move " + msgInfo.uri + " to SENT");
}
} else {
@@ -3294,8 +3330,7 @@
sendEvent(evt);
} else {
if (msgInfo.transparent == 0) {
- if (!Sms.moveMessageToFolder(context, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED,
- 0)) {
+ if (!Utils.moveMessageToFolder(context, msgInfo.uri, false)) {
Log.w(TAG, "Failed to move " + msgInfo.uri + " to FAILED");
}
} else {
@@ -3509,7 +3544,7 @@
if (result == Activity.RESULT_OK) {
Log.d(TAG, "actionMessageSentDisconnected: result OK");
if (transparent == 0) {
- if (!Sms.moveMessageToFolder(context, uri, Sms.MESSAGE_TYPE_SENT, 0)) {
+ if (!Utils.moveMessageToFolder(context, uri, true)) {
Log.d(TAG, "Failed to move " + uri + " to SENT");
}
} else {
@@ -3522,7 +3557,7 @@
} else */
{
if (transparent == 0) {
- if (!Sms.moveMessageToFolder(context, uri, Sms.MESSAGE_TYPE_FAILED, 0)) {
+ if (!Utils.moveMessageToFolder(context, uri, false)) {
Log.d(TAG, "Failed to move " + uri + " to FAILED");
}
} else {
@@ -3597,7 +3632,7 @@
if (msgInfo == null || !msgInfo.resend) {
continue;
}
- Sms.moveMessageToFolder(mContext, msgInfo.uri, Sms.MESSAGE_TYPE_FAILED, 0);
+ Utils.moveMessageToFolder(mContext, msgInfo.uri, false);
} while (c.moveToNext());
}
} finally {
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListing.java b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
index f33ea64..83a299e 100644
--- a/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListing.java
@@ -17,8 +17,8 @@
import android.util.Log;
import android.util.Xml;
+import com.android.bluetooth.Utils;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -91,7 +91,7 @@
*/
public byte[] encode() throws UnsupportedEncodingException {
StringWriter sw = new StringWriter();
- XmlSerializer xmlConvoElement = new FastXmlSerializer();
+ XmlSerializer xmlConvoElement = new FastXmlSerializer(0);
try {
xmlConvoElement.setOutput(sw);
xmlConvoElement.startDocument("UTF-8", true);
@@ -156,7 +156,7 @@
if (D) {
Log.i(TAG, "Unknown XML tag: " + name);
}
- XmlUtils.skipCurrentTag(parser);
+ Utils.skipCurrentTag(parser);
}
readConversations(parser);
}
@@ -191,7 +191,7 @@
if (D) {
Log.i(TAG, "Unknown XML tag: " + name);
}
- XmlUtils.skipCurrentTag(parser);
+ Utils.skipCurrentTag(parser);
continue;
}
// Add a single conversation
diff --git a/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
index be28be0..26a7fda 100644
--- a/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapConvoListingElement.java
@@ -17,8 +17,8 @@
import android.util.Log;
import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.Utils;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
-import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -341,7 +341,7 @@
if (D) {
Log.i(TAG, "Unknown XML tag: " + name);
}
- XmlUtils.skipCurrentTag(parser);
+ Utils.skipCurrentTag(parser);
continue;
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
index 560558e..b256211 100644
--- a/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapFolderElement.java
@@ -17,8 +17,8 @@
import android.util.Log;
import android.util.Xml;
+import com.android.bluetooth.Utils;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -266,7 +266,7 @@
public byte[] encode(int offset, int count) throws UnsupportedEncodingException {
StringWriter sw = new StringWriter();
- XmlSerializer xmlMsgElement = new FastXmlSerializer();
+ XmlSerializer xmlMsgElement = new FastXmlSerializer(0);
int i, stopIndex;
// We need index based access to the subFolders
BluetoothMapFolderElement[] folders =
@@ -346,7 +346,7 @@
if (D) {
Log.i(TAG, "Unknown XML tag: " + name);
}
- XmlUtils.skipCurrentTag(parser);
+ Utils.skipCurrentTag(parser);
}
readFolders(parser);
}
@@ -378,7 +378,7 @@
if (D) {
Log.i(TAG, "Unknown XML tag: " + name);
}
- XmlUtils.skipCurrentTag(parser);
+ Utils.skipCurrentTag(parser);
continue;
}
int count = parser.getAttributeCount();
diff --git a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index e3df91f..4755dfd 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
@@ -51,10 +52,16 @@
private static final int SDP_MAP_MSG_TYPE_MMS = 0x08;
private static final int SDP_MAP_MSG_TYPE_IM = 0x10;
- private static final int SDP_MAP_MAS_VERSION = 0x0102;
+ private static final String BLUETOOTH_MAP_VERSION_PROPERTY = "persist.bluetooth.mapversion";
+
+ private static final int SDP_MAP_MAS_VERSION_1_2 = 0x0102;
+ private static final int SDP_MAP_MAS_VERSION_1_3 = 0x0103;
+ private static final int SDP_MAP_MAS_VERSION_1_4 = 0x0104;
/* TODO: Should these be adaptive for each MAS? - e.g. read from app? */
- static final int SDP_MAP_MAS_FEATURES = 0x0000007F;
+ static final int SDP_MAP_MAS_FEATURES_1_2 = 0x0000007F;
+ static final int SDP_MAP_MAS_FEATURES_1_3 = 0x000603FF;
+ static final int SDP_MAP_MAS_FEATURES_1_4 = 0x000603FF;
private ServerSession mServerSession = null;
// The handle to the socket registration with SDP
@@ -80,7 +87,7 @@
private int mMasInstanceId = -1;
private boolean mEnableSmsMms = false;
BluetoothMapContentObserver mObserver;
-
+ private BluetoothMapObexServer mMapServer;
private AtomicLong mDbIndetifier = new AtomicLong();
private AtomicLong mFolderVersionCounter = new AtomicLong(0);
private AtomicLong mSmsMmsConvoListVersionCounter = new AtomicLong(0);
@@ -99,6 +106,7 @@
new HashMap<Long, BluetoothMapConvoListingElement>();
private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
+ private static int sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
public static final String TYPE_SMS_MMS_STR = "SMS/MMS";
public static final String TYPE_EMAIL_STR = "EMAIL";
@@ -339,9 +347,30 @@
}
}
+ final String currentValue = SystemProperties.get(BLUETOOTH_MAP_VERSION_PROPERTY, "map12");
+ int masVersion;
+
+ switch (currentValue) {
+ case "map12":
+ masVersion = SDP_MAP_MAS_VERSION_1_2;
+ sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
+ break;
+ case "map13":
+ masVersion = SDP_MAP_MAS_VERSION_1_3;
+ sFeatureMask = SDP_MAP_MAS_FEATURES_1_3;
+ break;
+ case "map14":
+ masVersion = SDP_MAP_MAS_VERSION_1_4;
+ sFeatureMask = SDP_MAP_MAS_FEATURES_1_4;
+ break;
+ default:
+ masVersion = SDP_MAP_MAS_VERSION_1_2;
+ sFeatureMask = SDP_MAP_MAS_FEATURES_1_2;
+ }
+
return SdpManager.getDefaultManager()
.createMapMasRecord(masName, mMasInstanceId, rfcommChannel, l2capPsm,
- SDP_MAP_MAS_VERSION, messageTypeFlags, SDP_MAP_MAS_FEATURES);
+ masVersion, messageTypeFlags, sFeatureMask);
}
/* Called for all MAS instances for each instance when auth. is completed, hence
@@ -360,17 +389,16 @@
}
mMnsClient = mnsClient;
- BluetoothMapObexServer mapServer;
mObserver = new BluetoothMapContentObserver(mContext, mMnsClient, this, mAccount,
mEnableSmsMms);
mObserver.init();
- mapServer =
+ mMapServer =
new BluetoothMapObexServer(mServiceHandler, mContext, mObserver, this, mAccount,
mEnableSmsMms);
-
+ mMapServer.setRemoteFeatureMask(mRemoteFeatureMask);
// setup transport
BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
- mServerSession = new ServerSession(transport, mapServer, null);
+ mServerSession = new ServerSession(transport, mMapServer, null);
if (D) {
Log.d(mTag, " ServerSession started.");
}
@@ -456,9 +484,11 @@
if (V) {
Log.v(mTag, "setRemoteFeatureMask : Curr: " + mRemoteFeatureMask);
}
- mRemoteFeatureMask = supportedFeatures & SDP_MAP_MAS_FEATURES;
+ mRemoteFeatureMask = supportedFeatures & sFeatureMask;
+ BluetoothMapUtils.savePeerSupportUtcTimeStamp(mRemoteFeatureMask);
if (mObserver != null) {
mObserver.setObserverRemoteFeatureMask(mRemoteFeatureMask);
+ mMapServer.setRemoteFeatureMask(mRemoteFeatureMask);
if (V) {
Log.v(mTag, "setRemoteFeatureMask : set: " + mRemoteFeatureMask);
}
@@ -469,6 +499,10 @@
return this.mRemoteFeatureMask;
}
+ public static int getFeatureMask() {
+ return sFeatureMask;
+ }
+
@Override
public synchronized boolean onConnect(BluetoothDevice device, BluetoothSocket socket) {
if (!mAcceptNewConnections) {
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
index dbe3b8c..9fc4dfc 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java
@@ -103,7 +103,7 @@
xmlMsgElement.setOutput(sw);
xmlMsgElement.text("\n");
} else {
- xmlMsgElement = new FastXmlSerializer();
+ xmlMsgElement = new FastXmlSerializer(0);
xmlMsgElement.setOutput(sw);
xmlMsgElement.startDocument("UTF-8", true);
xmlMsgElement.setFeature(
diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
index 17fc549..8e8f216 100644
--- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
+++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java
@@ -288,7 +288,8 @@
}
if (mDateTime != 0) {
- xmlMsgElement.attribute(null, "datetime", this.getDateTimeString());
+ xmlMsgElement.attribute(null, "datetime",
+ BluetoothMapUtils.getDateTimeString(this.getDateTime()));
}
if (mSenderName != null) {
xmlMsgElement.attribute(null, "sender_name",
diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index 3b35262..15a7840 100644
--- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -324,6 +324,11 @@
}
this.mRemoteFeatureMask = mRemoteFeatureMask;
this.mOutContent.setRemoteFeatureMask(mRemoteFeatureMask);
+ if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT)
+ == BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT) {
+ mMessageVersion = BluetoothMapUtils.MAP_V11_STR;
+ }
+ if (V) Log.d(TAG," setRemoteFeatureMask mMessageVersion :" + mMessageVersion);
}
@Override
diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java
index 1d651ba..ee2e3c2 100644
--- a/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -36,6 +36,7 @@
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -585,18 +586,18 @@
}
}
- boolean setPriority(BluetoothDevice device, int priority) {
+ boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
if (VERBOSE) {
- Log.v(TAG, "Saved priority " + device + " = " + priority);
+ Log.v(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.MAP, priority);
+ .setProfileConnectionPolicy(device, BluetoothProfile.MAP, connectionPolicy);
return true;
}
- int getPriority(BluetoothDevice device) {
+ int getConnectionPolicy(BluetoothDevice device) {
return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.MAP);
+ .getProfileConnectionPolicy(device, BluetoothProfile.MAP);
}
@Override
@@ -637,7 +638,8 @@
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAppObserver = new BluetoothMapAppObserver(this, this);
- mSmsCapable = getResources().getBoolean(com.android.internal.R.bool.config_sms_capable);
+ TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+ mSmsCapable = tm.isSmsCapable();
mEnabledAccounts = mAppObserver.getEnabledAccountItems();
createMasInstances(); // Uses mEnabledAccounts
@@ -1258,21 +1260,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
BluetoothMapService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
BluetoothMapService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
index f6af8db..3473b7a 100644
--- a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
+++ b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java
@@ -16,16 +16,15 @@
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+import android.content.Context;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.util.Log;
-import com.android.internal.telephony.GsmAlphabet;
+import com.android.bluetooth.util.GsmAlphabet;
import com.android.internal.telephony.SmsConstants;
-import com.android.internal.telephony.SmsHeader;
-import com.android.internal.telephony.cdma.sms.BearerData;
-import com.android.internal.telephony.cdma.sms.UserData;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -45,6 +44,18 @@
public static final int SMS_TYPE_GSM = 1;
public static final int SMS_TYPE_CDMA = 2;
+ /**
+ * from SMS user data header information element identifiers.
+ * (see TS 23.040 9.2.3.24)
+ */
+ private static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24;
+ private static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25;
+
+ /**
+ * Supported message types for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
+ */
+ private static final int MESSAGE_TYPE_DELIVER = 0x01;
/* We need to handle the SC-address mentioned in errata 4335.
* Since the definition could be read in three different ways, I have asked
@@ -259,7 +270,7 @@
// Mask out the type
tmp &= 0x0f;
// Set the new type
- tmp |= ((BearerData.MESSAGE_TYPE_DELIVER << 4) & 0xf0);
+ tmp |= ((MESSAGE_TYPE_DELIVER << 4) & 0xf0);
// Store the result
mData[offset + 2] = (byte) tmp;
@@ -341,9 +352,9 @@
} catch (IOException e) {
Log.w(TAG, "unable to read userDataHeader", e);
}
- SmsHeader userDataHeader = SmsHeader.fromByteArray(udh);
- mLanguageTable = userDataHeader.languageTable;
- mLanguageShiftTable = userDataHeader.languageShiftTable;
+ int[] tableValue = getTableFromByteArray(udh);
+ mLanguageTable = tableValue[0];
+ mLanguageShiftTable = tableValue[1];
int headerBits = (userDataHeaderLength + 1) * 8;
int headerSeptets = headerBits / 7;
@@ -502,37 +513,34 @@
return sConcatenatedRef;
}
- public static ArrayList<SmsPdu> getSubmitPdus(String messageText, String address) {
+ public static ArrayList<SmsPdu> getSubmitPdus(Context context, String messageText,
+ String address) {
/* Use the generic GSM/CDMA SMS Message functionality within Android to generate the
* SMS PDU's as once generated to send the SMS message.
*/
- int activePhone = TelephonyManager.getDefault()
- .getCurrentPhoneType(); // TODO: Change to use: ((TelephonyManager)myContext
- // .getSystemService(Context.TELEPHONY_SERVICE))
+ int activePhone = context.getSystemService(TelephonyManager.class)
+ .getCurrentPhoneType();
int phoneType;
- GsmAlphabet.TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone)
- ? com.android.internal.telephony.cdma.SmsMessage.calculateLength(
- (CharSequence) messageText, false, true)
- : com.android.internal.telephony.gsm.SmsMessage.calculateLength(
- (CharSequence) messageText, false);
+ int[] ted = SmsMessage.calculateLength((CharSequence) messageText, false);
SmsPdu newPdu;
String destinationAddress;
- int msgCount = ted.msgCount;
+ int msgCount = ted[0];
int encoding;
int languageTable;
int languageShiftTable;
int refNumber = getNextConcatenatedRef() & 0x00FF;
- ArrayList<String> smsFragments = SmsMessage.fragmentText(messageText);
+ SmsManager smsMng = SmsManager.getDefault();
+ ArrayList<String> smsFragments = smsMng.divideMessage(messageText);
ArrayList<SmsPdu> pdus = new ArrayList<SmsPdu>(msgCount);
byte[] data;
// Default to GSM, as this code should not be used, if we neither have CDMA not GSM.
phoneType = (activePhone == PHONE_TYPE_CDMA) ? SMS_TYPE_CDMA : SMS_TYPE_GSM;
- encoding = ted.codeUnitSize;
- languageTable = ted.languageTable;
- languageShiftTable = ted.languageShiftTable;
+ encoding = ted[3];
+ languageTable = ted[4];
+ languageShiftTable = ted[5];
destinationAddress = PhoneNumberUtils.stripSeparators(address);
if (destinationAddress == null || destinationAddress.length() < 2) {
destinationAddress =
@@ -548,48 +556,9 @@
/* This code is a reduced copy of the actual code used in the Android SMS sub system,
* hence the comments have been left untouched. */
for (int i = 0; i < msgCount; i++) {
- SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
- concatRef.refNumber = refNumber;
- concatRef.seqNumber = i + 1; // 1-based sequence
- concatRef.msgCount = msgCount;
- // We currently set this to true since our messaging app will never
- // send more than 255 parts (it converts the message to MMS well before that).
- // However, we should support 3rd party messaging apps that might need 16-bit
- // references
- // Note: It's not sufficient to just flip this bit to true; it will have
- // ripple effects (several calculations assume 8-bit ref).
- concatRef.isEightBits = true;
- SmsHeader smsHeader = new SmsHeader();
- smsHeader.concatRef = concatRef;
-
- /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding
- * will be determined(again) by getSubmitPdu().
- * All packets need to be encoded using the same encoding, as the bMessage
- * only have one filed to describe the encoding for all messages in a concatenated
- * SMS... */
- if (encoding == SmsConstants.ENCODING_7BIT) {
- smsHeader.languageTable = languageTable;
- smsHeader.languageShiftTable = languageShiftTable;
- }
-
- if (phoneType == SMS_TYPE_GSM) {
- data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
- destinationAddress, smsFragments.get(i), false,
- SmsHeader.toByteArray(smsHeader), encoding, languageTable,
- languageShiftTable).encodedMessage;
- } else { // SMS_TYPE_CDMA
- UserData uData = new UserData();
- uData.payloadStr = smsFragments.get(i);
- uData.userDataHeader = smsHeader;
- if (encoding == SmsConstants.ENCODING_7BIT) {
- uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
- } else { // assume UTF-16
- uData.msgEncoding = UserData.ENCODING_UNICODE_16;
- }
- uData.msgEncodingSet = true;
- data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
- destinationAddress, uData, false).encodedMessage;
- }
+ data = SmsMessage.getSubmitPduEncodedMessage(phoneType == SMS_TYPE_GSM,
+ destinationAddress, smsFragments.get(i), encoding, languageTable,
+ languageShiftTable, refNumber, i + 1, msgCount);
newPdu = new SmsPdu(data, encoding, phoneType, languageTable);
pdus.add(newPdu);
}
@@ -607,8 +576,9 @@
* @param date The delivery time stamp.
* @return
*/
- public static ArrayList<SmsPdu> getDeliverPdus(String messageText, String address, long date) {
- ArrayList<SmsPdu> deliverPdus = getSubmitPdus(messageText, address);
+ public static ArrayList<SmsPdu> getDeliverPdus(Context context, String messageText,
+ String address, long date) {
+ ArrayList<SmsPdu> deliverPdus = getSubmitPdus(context, messageText, address);
/*
* For CDMA the only difference between deliver and submit pdus are the messageType,
@@ -635,11 +605,13 @@
* The destination address must be extracted from the bmessage vCard(s).
*/
public static String decodePdu(byte[] data, int type) {
- String ret;
+ String ret = "";
if (type == SMS_TYPE_CDMA) {
/* This is able to handle both submit and deliver PDUs */
- ret = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(0, data)
- .getMessageBody();
+ SmsMessage smsMessage = SmsMessage.createFromNativeSmsSubmitPdu(data, true);
+ if (smsMessage != null) {
+ ret = smsMessage.getMessageBody();
+ }
} else {
/* For GSM, there is no submit pdu decoder, and most parser utils are private, and
only minded for submit pdus */
@@ -786,4 +758,26 @@
return messageBody;
}
+ private static int[] getTableFromByteArray(byte[] data) {
+ ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+ /** tableValue[0]: languageTable
+ * tableValue[1]: languageShiftTable */
+ int[] tableValue = new int[2];
+ while (inStream.available() > 0) {
+ int id = inStream.read();
+ int length = inStream.read();
+ switch (id) {
+ case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
+ tableValue[1] = inStream.read();
+ break;
+ case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
+ tableValue[0] = inStream.read();
+ break;
+ default:
+ inStream.skip(length);
+ }
+ }
+ return tableValue;
+ }
+
}
diff --git a/src/com/android/bluetooth/map/BluetoothMapUtils.java b/src/com/android/bluetooth/map/BluetoothMapUtils.java
index e867bd6..a3710a3 100644
--- a/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -27,7 +27,7 @@
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.BitSet;
-import java.util.Date;
+import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -96,6 +96,8 @@
static final int MAP_MESSAGE_LISTING_FORMAT_V10 = 10; // MAP spec below 1.3
static final int MAP_MESSAGE_LISTING_FORMAT_V11 = 11; // MAP spec 1.3
+ private static boolean mPeerSupportUtcTimeStamp = false;
+
/**
* This enum is used to convert from the bMessage type property to a type safe
* type. Hence do not change the names of the enum values.
@@ -112,13 +114,6 @@
}
}
- public static String getDateTimeString(long timestamp) {
- SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
- Date date = new Date(timestamp);
- return format.format(date); // Format to YYYYMMDDTHHMMSS local time
- }
-
-
public static void printCursor(Cursor c) {
if (D) {
StringBuilder sb = new StringBuilder();
@@ -671,5 +666,26 @@
}
}
+
+ static String getDateTimeString( long timestamp) {
+ SimpleDateFormat format = (mPeerSupportUtcTimeStamp) ? new
+ SimpleDateFormat("yyyyMMdd'T'HHmmssZ") : new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(timestamp);
+ if (V) Log.v(TAG, "getDateTimeString timestamp :" + timestamp + " time:"
+ + format.format(cal.getTime()));
+ return format.format(cal.getTime());
+ }
+
+ static void savePeerSupportUtcTimeStamp(int remoteFeatureMask) {
+ if ((remoteFeatureMask & MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT)
+ == MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT) {
+ mPeerSupportUtcTimeStamp = true;
+ } else {
+ mPeerSupportUtcTimeStamp = false;
+ }
+ if (V) Log.v(TAG, "savePeerSupportUtcTimeStamp " + mPeerSupportUtcTimeStamp);
+ }
+
}
diff --git a/src/com/android/bluetooth/mapclient/MapClientService.java b/src/com/android/bluetooth/mapclient/MapClientService.java
index 9467683..96b8efb 100644
--- a/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -101,8 +101,9 @@
Log.d(TAG, "MAP connect device: " + device
+ ", InstanceMap start state: " + sb.toString());
}
- if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
- Log.w(TAG, "Connection not allowed: <" + device.getAddress() + "> is PRIORITY_OFF");
+ if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ Log.w(TAG, "Connection not allowed: <" + device.getAddress()
+ + "> is CONNECTION_POLICY_FORBIDDEN");
return false;
}
MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
@@ -217,18 +218,50 @@
: mapStateMachine.getState();
}
- public boolean setPriority(BluetoothDevice device, int priority) {
+ /**
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
if (VDBG) {
- Log.v(TAG, "Saved priority " + device + " = " + priority);
+ Log.v(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.MAP_CLIENT, priority);
+ .setProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT, connectionPolicy);
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
return true;
}
- public int getPriority(BluetoothDevice device) {
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.MAP_CLIENT);
+ .getProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT);
}
public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
@@ -484,21 +517,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
MapClientService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
MapClientService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
@Override
diff --git a/src/com/android/bluetooth/mapclient/MasClient.java b/src/com/android/bluetooth/mapclient/MasClient.java
index 6991704..2966f21 100644
--- a/src/com/android/bluetooth/mapclient/MasClient.java
+++ b/src/com/android/bluetooth/mapclient/MasClient.java
@@ -26,7 +26,7 @@
import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
-import com.android.internal.util.StateMachine;
+import com.android.bluetooth.statemachine.StateMachine;
import java.io.IOException;
import java.lang.ref.WeakReference;
diff --git a/src/com/android/bluetooth/mapclient/MceStateMachine.java b/src/com/android/bluetooth/mapclient/MceStateMachine.java
index 9b86aae..85ba60e 100644
--- a/src/com/android/bluetooth/mapclient/MceStateMachine.java
+++ b/src/com/android/bluetooth/mapclient/MceStateMachine.java
@@ -59,10 +59,10 @@
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.map.BluetoothMapbMessageMime;
+import com.android.bluetooth.statemachine.IState;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IState;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
import com.android.vcard.VCardConstants;
import com.android.vcard.VCardEntry;
import com.android.vcard.VCardProperty;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppBatch.java b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
index a66a667..b2c9cd4 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppBatch.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppBatch.java
@@ -37,8 +37,6 @@
import android.content.Context;
import android.util.Log;
-import com.google.android.collect.Lists;
-
import java.io.File;
import java.util.ArrayList;
@@ -105,7 +103,7 @@
public BluetoothOppBatch(Context context, BluetoothOppShareInfo info) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
mContext = context;
- mShares = Lists.newArrayList();
+ mShares = new ArrayList();
mTimestamp = info.mTimestamp;
mDirection = info.mDirection;
mDestination = adapter.getRemoteDevice(info.mDestination);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
index 53b785f..cc24c8d 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
@@ -21,7 +21,6 @@
import android.content.IntentFilter;
import android.content.pm.ProviderInfo;
import android.net.Uri;
-import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -68,8 +67,7 @@
if (!mRegisteredReceiver) {
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
- mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.CURRENT, userFilter,
- null, null);
+ mContext.registerReceiver(mBroadcastReceiver, userFilter, null, null);
mRegisteredReceiver = true;
}
UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppService.java b/src/com/android/bluetooth/opp/BluetoothOppService.java
index 914b9b6..d084dd6 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -61,8 +61,6 @@
import com.android.bluetooth.sdp.SdpManager;
import com.android.internal.annotations.VisibleForTesting;
-import com.google.android.collect.Lists;
-
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -196,8 +194,8 @@
if (V) {
Log.v(TAG, "onCreate");
}
- mShares = Lists.newArrayList();
- mBatches = Lists.newArrayList();
+ mShares = new ArrayList();
+ mBatches = new ArrayList();
mBatchId = 1;
final ContentResolver contentResolver = getContentResolver();
new Thread("trimDatabase") {
diff --git a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 06562e4..94c2fa3 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -140,9 +140,9 @@
ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
if (D) {
Log.d(TAG, "Received UUID: " + uuid.toString());
- Log.d(TAG, "expected UUID: " + BluetoothUuid.ObexObjectPush.toString());
+ Log.d(TAG, "expected UUID: " + BluetoothUuid.OBEX_OBJECT_PUSH.toString());
}
- if (uuid.equals(BluetoothUuid.ObexObjectPush)) {
+ if (uuid.equals(BluetoothUuid.OBEX_OBJECT_PUSH)) {
int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
Log.d(TAG, " -> status: " + status);
BluetoothDevice device =
@@ -637,7 +637,7 @@
private void startConnectSession() {
mDevice = mBatch.mDestination;
- if (!mBatch.mDestination.sdpSearch(BluetoothUuid.ObexObjectPush)) {
+ if (!mBatch.mDestination.sdpSearch(BluetoothUuid.OBEX_OBJECT_PUSH)) {
if (D) {
Log.d(TAG, "SDP failed, start rfcomm connect directly");
}
@@ -722,7 +722,7 @@
return;
}
mBtSocket = mDevice.createInsecureRfcommSocketToServiceRecord(
- BluetoothUuid.ObexObjectPush.getUuid());
+ BluetoothUuid.OBEX_OBJECT_PUSH.getUuid());
} catch (IOException e1) {
Log.e(TAG, "Rfcomm socket create error", e1);
markConnectionFailed(mBtSocket);
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
index 7533d0d..2f89635 100644
--- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java
+++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -50,8 +50,6 @@
import com.android.bluetooth.R;
-import com.google.android.collect.Lists;
-
import java.io.File;
import java.io.IOException;
import java.math.RoundingMode;
@@ -147,7 +145,7 @@
*/
// This function is used when UI show batch transfer. Currently only show single transfer.
public static ArrayList<String> queryTransfersInBatch(Context context, Long timeStamp) {
- ArrayList<String> uris = Lists.newArrayList();
+ ArrayList<String> uris = new ArrayList();
final String where = BluetoothShare.TIMESTAMP + " == " + timeStamp;
Cursor metadataCursor =
context.getContentResolver().query(BluetoothShare.CONTENT_URI, new String[]{
diff --git a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
index 72016f7..4037682 100644
--- a/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
+++ b/src/com/android/bluetooth/pan/BluetoothTetheringNetworkFactory.java
@@ -32,7 +32,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.text.TextUtils;
-import android.util.Slog;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -95,7 +95,7 @@
.withoutIpReachabilityMonitor()
.build().toStableParcelable());
} catch (RemoteException e) {
- Slog.e(TAG, "Error starting IpClient provisioning", e);
+ Log.e(TAG, "Error starting IpClient provisioning", e);
}
}
}
@@ -117,7 +117,7 @@
try {
mIpClient.shutdown();
} catch (RemoteException e) {
- Slog.e(TAG, "Error shutting down IpClient", e);
+ Log.e(TAG, "Error shutting down IpClient", e);
}
mIpClient = null;
}
@@ -146,7 +146,7 @@
synchronized (BluetoothTetheringNetworkFactory.this) {
if (TextUtils.isEmpty(mInterfaceName)) {
- Slog.e(TAG, "attempted to reverse tether without interface name");
+ Log.e(TAG, "attempted to reverse tether without interface name");
return;
}
log("ipProvisioningThread(+" + mInterfaceName + "): " + "mNetworkInfo="
@@ -157,7 +157,7 @@
linkProperties = ipcCallback.waitForProvisioning();
if (linkProperties == null) {
- Slog.e(TAG, "IP provisioning error.");
+ Log.e(TAG, "IP provisioning error.");
synchronized (BluetoothTetheringNetworkFactory.this) {
stopIpClientLocked();
setScoreFilter(-1);
@@ -212,12 +212,12 @@
// becomes available. We register our NetworkFactory at this point.
public void startReverseTether(final String iface) {
if (iface == null || TextUtils.isEmpty(iface)) {
- Slog.e(TAG, "attempted to reverse tether with empty interface");
+ Log.e(TAG, "attempted to reverse tether with empty interface");
return;
}
synchronized (this) {
if (!TextUtils.isEmpty(mInterfaceName)) {
- Slog.e(TAG, "attempted to reverse tether while already in process");
+ Log.e(TAG, "attempted to reverse tether while already in process");
return;
}
mInterfaceName = iface;
@@ -231,7 +231,7 @@
// goes away. We stop advertising ourselves to ConnectivityService at this point.
public synchronized void stopReverseTether() {
if (TextUtils.isEmpty(mInterfaceName)) {
- Slog.e(TAG, "attempted to stop reverse tether with nothing tethered");
+ Log.e(TAG, "attempted to stop reverse tether with nothing tethered");
return;
}
onCancelRequest();
diff --git a/src/com/android/bluetooth/pan/PanService.java b/src/com/android/bluetooth/pan/PanService.java
index aa5a1fd..2818441 100644
--- a/src/com/android/bluetooth/pan/PanService.java
+++ b/src/com/android/bluetooth/pan/PanService.java
@@ -18,15 +18,17 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothPan.LocalPanRole;
+import android.bluetooth.BluetoothPan.RemotePanRole;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothPan;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources.NotFoundException;
import android.net.ConnectivityManager;
+import android.net.InetAddresses;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
-import android.net.NetworkUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
@@ -248,6 +250,15 @@
}
@Override
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ PanService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+
+ @Override
public int getConnectionState(BluetoothDevice device) {
PanService service = getService();
if (service == null) {
@@ -359,6 +370,7 @@
public boolean isTetheringOn() {
// TODO(BT) have a variable marking the on/off state
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mTetherOn;
}
@@ -385,26 +397,54 @@
}
}
- public boolean setPriority(BluetoothDevice device, int priority) {
- if (device == null) {
- throw new IllegalArgumentException("Null device");
- }
+ /**
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
+ Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
- AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.PAN, priority);
- return true;
+ boolean setSuccessfully;
+ setSuccessfully = AdapterService.getAdapterService().getDatabase()
+ .setProfileConnectionPolicy(device, BluetoothProfile.PAN, connectionPolicy);
+ if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (setSuccessfully
+ && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
+ return setSuccessfully;
}
- public int getPriority(BluetoothDevice device) {
- if (device == null) {
- throw new IllegalArgumentException("Null device");
- }
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.PAN);
+ .getProfileConnectionPolicy(device, BluetoothProfile.PAN);
}
public List<BluetoothDevice> getConnectedDevices() {
@@ -485,8 +525,8 @@
}
}
- void handlePanDeviceStateChange(BluetoothDevice device, String iface, int state, int localRole,
- int remoteRole) {
+ void handlePanDeviceStateChange(BluetoothDevice device, String iface, int state,
+ @LocalPanRole int localRole, @RemotePanRole int remoteRole) {
if (DBG) {
Log.d(TAG, "handlePanDeviceStateChange: device: " + device + ", iface: " + iface
+ ", state: " + state + ", localRole:" + localRole + ", remoteRole:"
@@ -625,10 +665,10 @@
InetAddress addr = null;
LinkAddress linkAddr = ifcg.getLinkAddress();
if (linkAddr == null || (addr = linkAddr.getAddress()) == null || addr.equals(
- NetworkUtils.numericToInetAddress("0.0.0.0")) || addr.equals(
- NetworkUtils.numericToInetAddress("::0"))) {
+ InetAddresses.parseNumericAddress("0.0.0.0")) || addr.equals(
+ InetAddresses.parseNumericAddress("::0"))) {
address = BLUETOOTH_IFACE_ADDR_START;
- addr = NetworkUtils.numericToInetAddress(address);
+ addr = InetAddresses.parseNumericAddress(address);
}
ifcg.setLinkAddress(new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH));
@@ -637,8 +677,6 @@
} else {
ifcg.setInterfaceDown();
}
-
- ifcg.clearFlag("running");
service.setInterfaceConfig(iface, ifcg);
if (enable) {
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 466ecc4..04680fa 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -50,7 +50,6 @@
import android.os.PowerManager;
import android.os.UserManager;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.util.Log;
import com.android.bluetooth.IObexConnectionHandler;
@@ -427,7 +426,8 @@
* {@link BluetoothProfile#STATE_DISCONNECTING}
*/
public int getConnectionState(BluetoothDevice device) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ enforceCallingOrSelfPermission(
+ BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
if (mPbapStateMachineMap == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
@@ -469,7 +469,38 @@
return devices;
}
- void disconnect(BluetoothDevice device) {
+ /**
+ * Disconnects Pbap if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy determines whether pbap should be disconnected
+ * @return true if pbap is disconnected, false otherwise
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ enforceCallingOrSelfPermission(
+ BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
+ if (DEBUG) {
+ Log.d(TAG, "setConnectionPolicy: device " + device
+ + " and connectionPolicy " + connectionPolicy);
+ }
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Disconnects pbap server profile with device
+ * @param device is the remote bluetooth device
+ */
+ public void disconnect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
synchronized (mPbapStateMachineMap) {
PbapStateMachine sm = mPbapStateMachineMap.get(device);
@@ -654,6 +685,19 @@
}
@Override
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ if (DEBUG) {
+ Log.d(TAG, "setConnectionPolicy for device: " + device + ", policy:"
+ + connectionPolicy);
+ }
+ BluetoothPbapService service = getService();
+ if (service == null) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+
+ @Override
public void disconnect(BluetoothDevice device) {
if (DEBUG) {
Log.d(TAG, "disconnect");
@@ -786,10 +830,7 @@
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
sLocalPhoneNum = tm.getLine1Number();
- sLocalPhoneName = tm.getLine1AlphaTag();
- if (TextUtils.isEmpty(sLocalPhoneName)) {
- sLocalPhoneName = this.getString(R.string.localPhoneName);
- }
+ sLocalPhoneName = this.getString(R.string.localPhoneName);
}
if (VERBOSE)
Log.v(TAG, "Local Phone Details- Number:" + sLocalPhoneNum
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 53670ff..4e4a240 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -99,7 +99,7 @@
private static final long PBAP_FILTER_NICKNAME = 1 << 23;
private static final int PBAP_SUPPORTED_FEATURE =
- PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING;
+ PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_DOWNLOADING;
private static final long PBAP_REQUESTED_FIELDS =
PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO
| PBAP_FILTER_ADR | PBAP_FILTER_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME;
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientService.java b/src/com/android/bluetooth/pbapclient/PbapClientService.java
index 16b02e1..9446af1 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientService.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientService.java
@@ -19,9 +19,11 @@
import android.accounts.Account;
import android.accounts.AccountManager;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothPbapClient;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -31,6 +33,7 @@
import com.android.bluetooth.R;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.hfpclient.connserv.HfpClientConnectionService;
import com.android.bluetooth.sdp.SdpManager;
import java.util.ArrayList;
@@ -71,6 +74,9 @@
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
// delay initial download until after the user is unlocked to add an account.
filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ // To remove call logs when PBAP was never connected while calls were made,
+ // we also listen for HFP to become disconnected.
+ filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
try {
registerReceiver(mPbapBroadcastReceiver, filter);
} catch (Exception e) {
@@ -128,6 +134,21 @@
}
}
+ private void removeHfpCallLog(String accountName, Context context) {
+ if (DBG) Log.d(TAG, "Removing call logs from " + accountName);
+ // Delete call logs belonging to accountName==BD_ADDR that also match
+ // component name "hfpclient".
+ ComponentName componentName = new ComponentName(context, HfpClientConnectionService.class);
+ String selectionFilter = CallLog.Calls.PHONE_ACCOUNT_ID + "=? AND "
+ + CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME + "=?";
+ String[] selectionArgs = new String[]{accountName, componentName.flattenToString()};
+ try {
+ getContentResolver().delete(CallLog.Calls.CONTENT_URI, selectionFilter, selectionArgs);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Call Logs could not be deleted, they may not exist yet.");
+ }
+ }
+
private void registerSdpRecord() {
SdpManager sdpManager = SdpManager.getDefaultManager();
if (sdpManager == null) {
@@ -171,6 +192,21 @@
for (PbapClientStateMachine stateMachine : mPbapClientStateMachineMap.values()) {
stateMachine.resumeDownload();
}
+ } else if (action.equals(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED)) {
+ // PbapClientConnectionHandler has code to remove calllogs when PBAP disconnects.
+ // However, if PBAP was never connected/enabled in the first place, and calls are
+ // made over HFP, these calllogs will not be removed when the device disconnects.
+ // This code ensures callogs are still removed in this case.
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+
+ if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ if (DBG) {
+ Log.d(TAG, "Received intent to disconnect HFP with " + device);
+ }
+ // HFP client stores entries in calllog.db by BD_ADDR and component name
+ removeHfpCallLog(device.getAddress(), context);
+ }
}
}
}
@@ -253,21 +289,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
PbapClientService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
PbapClientService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
@@ -299,7 +335,7 @@
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
if (DBG) Log.d(TAG, "Received request to ConnectPBAPPhonebook " + device.getAddress());
- if (getPriority(device) <= BluetoothProfile.PRIORITY_OFF) {
+ if (getConnectionPolicy(device) <= BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
return false;
}
synchronized (mPbapClientStateMachineMap) {
@@ -375,26 +411,58 @@
}
}
- public boolean setPriority(BluetoothDevice device, int priority) {
+ /**
+ * Set connection policy of the profile and connects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
if (device == null) {
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
if (DBG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
+ Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.PBAP_CLIENT, priority);
+ .setProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT, connectionPolicy);
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ connect(device);
+ } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
return true;
}
- public int getPriority(BluetoothDevice device) {
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
if (device == null) {
throw new IllegalArgumentException("Null device");
}
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.PBAP_CLIENT);
+ .getProfileConnectionPolicy(device, BluetoothProfile.PBAP_CLIENT);
}
@Override
diff --git a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
index 2eaf8e5..71e6c67 100644
--- a/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
+++ b/src/com/android/bluetooth/pbapclient/PbapClientStateMachine.java
@@ -59,9 +59,9 @@
import com.android.bluetooth.BluetoothMetricsProto;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
-import com.android.internal.util.IState;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
+import com.android.bluetooth.statemachine.IState;
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/bluetooth/sap/SapService.java b/src/com/android/bluetooth/sap/SapService.java
index 995051e..bd02812 100644
--- a/src/com/android/bluetooth/sap/SapService.java
+++ b/src/com/android/bluetooth/sap/SapService.java
@@ -595,18 +595,47 @@
}
}
- public boolean setPriority(BluetoothDevice device, int priority) {
+ /**
+ * Set connection policy of the profile and disconnects it if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ */
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
if (DEBUG) {
- Log.d(TAG, "Saved priority " + device + " = " + priority);
+ Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
}
AdapterService.getAdapterService().getDatabase()
- .setProfilePriority(device, BluetoothProfile.SAP, priority);
+ .setProfileConnectionPolicy(device, BluetoothProfile.SAP, connectionPolicy);
+ if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+ disconnect(device);
+ }
return true;
}
- public int getPriority(BluetoothDevice device) {
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ public int getConnectionPolicy(BluetoothDevice device) {
return AdapterService.getAdapterService().getDatabase()
- .getProfilePriority(device, BluetoothProfile.SAP);
+ .getProfileConnectionPolicy(device, BluetoothProfile.SAP);
}
@Override
@@ -801,8 +830,9 @@
Log.v(TAG, "setSimAccessPermission(ACCESS_ALLOWED) result=" + result);
}
}
- boolean result = setPriority(mRemoteDevice, BluetoothProfile.PRIORITY_ON);
- Log.d(TAG, "setPriority ON, result = " + result);
+ boolean result = setConnectionPolicy(mRemoteDevice,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ Log.d(TAG, "setConnectionPolicy ALLOWED, result = " + result);
try {
if (mConnSocket != null) {
@@ -822,8 +852,9 @@
Log.v(TAG, "setSimAccessPermission(ACCESS_REJECTED) result=" + result);
}
}
- boolean result = setPriority(mRemoteDevice, BluetoothProfile.PRIORITY_OFF);
- Log.d(TAG, "setPriority OFF, result = " + result);
+ boolean result = setConnectionPolicy(mRemoteDevice,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ Log.d(TAG, "setConnectionPolicy FORBIDDEN, result = " + result);
// Ensure proper cleanup, and prepare for new connect.
mSessionStatusHandler.sendEmptyMessage(MSG_SERVERSESSION_CLOSE);
}
@@ -983,21 +1014,21 @@
}
@Override
- public boolean setPriority(BluetoothDevice device, int priority) {
+ public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
SapService service = getService();
if (service == null) {
return false;
}
- return service.setPriority(device, priority);
+ return service.setConnectionPolicy(device, connectionPolicy);
}
@Override
- public int getPriority(BluetoothDevice device) {
+ public int getConnectionPolicy(BluetoothDevice device) {
SapService service = getService();
if (service == null) {
- return BluetoothProfile.PRIORITY_UNDEFINED;
+ return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
}
- return service.getPriority(device);
+ return service.getConnectionPolicy(device);
}
}
}
diff --git a/src/com/android/bluetooth/statemachine/IState.java b/src/com/android/bluetooth/statemachine/IState.java
new file mode 100644
index 0000000..559975f
--- /dev/null
+++ b/src/com/android/bluetooth/statemachine/IState.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.bluetooth.statemachine;
+
+import android.os.Message;
+
+/**
+ * @hide
+ *
+ * The interface for implementing states in a {@link StateMachine}
+ */
+public interface IState {
+
+ /**
+ * Returned by processMessage to indicate the message was processed.
+ */
+ static final boolean HANDLED = true;
+
+ /**
+ * Returned by processMessage to indicate the message was NOT processed.
+ */
+ static final boolean NOT_HANDLED = false;
+
+ /**
+ * Called when a state is entered.
+ */
+ void enter();
+
+ /**
+ * Called when a state is exited.
+ */
+ void exit();
+
+ /**
+ * Called when a message is to be processed by the
+ * state machine.
+ *
+ * This routine is never reentered thus no synchronization
+ * is needed as only one processMessage method will ever be
+ * executing within a state machine at any given time. This
+ * does mean that processing by this routine must be completed
+ * as expeditiously as possible as no subsequent messages will
+ * be processed until this routine returns.
+ *
+ * @param msg to process
+ * @return HANDLED if processing has completed and NOT_HANDLED
+ * if the message wasn't processed.
+ */
+ boolean processMessage(Message msg);
+
+ /**
+ * Name of State for debugging purposes.
+ *
+ * @return name of state.
+ */
+ String getName();
+}
diff --git a/src/com/android/bluetooth/statemachine/State.java b/src/com/android/bluetooth/statemachine/State.java
new file mode 100644
index 0000000..358938f
--- /dev/null
+++ b/src/com/android/bluetooth/statemachine/State.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 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.bluetooth.statemachine;
+
+import android.os.Message;
+
+/**
+ * @hide
+ *
+ * The class for implementing states in a StateMachine
+ */
+public class State implements IState {
+
+ /**
+ * Constructor
+ */
+ protected State() {
+ }
+
+ @Override
+ public void enter() {
+ }
+
+ @Override
+ public void exit() {
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ return false;
+ }
+
+ /**
+ * Name of State for debugging purposes.
+ *
+ * This default implementation returns the class name, returning
+ * the instance name would better in cases where a State class
+ * is used for multiple states. But normally there is one class per
+ * state and the class name is sufficient and easy to get. You may
+ * want to provide a setName or some other mechanism for setting
+ * another name if the class name is not appropriate.
+ *
+ */
+ @Override
+ public String getName() {
+ String name = getClass().getName();
+ int lastDollar = name.lastIndexOf('$');
+ return name.substring(lastDollar + 1);
+ }
+}
diff --git a/src/com/android/bluetooth/statemachine/StateMachine.java b/src/com/android/bluetooth/statemachine/StateMachine.java
new file mode 100644
index 0000000..a21b59c
--- /dev/null
+++ b/src/com/android/bluetooth/statemachine/StateMachine.java
@@ -0,0 +1,2167 @@
+/*
+ * Copyright (C) 2019 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.bluetooth.statemachine;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+
+/**
+ *
+ * @hide
+ *
+ * <p>The state machine defined here is a hierarchical state machine which processes messages
+ * and can have states arranged hierarchically.</p>
+ *
+ * <p>A state is a <code>State</code> object and must implement
+ * <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
+ * The enter/exit methods are equivalent to the construction and destruction
+ * in Object Oriented programming and are used to perform initialization and
+ * cleanup of the state respectively. The <code>getName</code> method returns the
+ * name of the state; the default implementation returns the class name. It may be
+ * desirable to have <code>getName</code> return the state instance name instead,
+ * in particular if a particular state class has multiple instances.</p>
+ *
+ * <p>When a state machine is created, <code>addState</code> is used to build the
+ * hierarchy and <code>setInitialState</code> is used to identify which of these
+ * is the initial state. After construction the programmer calls <code>start</code>
+ * which initializes and starts the state machine. The first action the StateMachine
+ * is to the invoke <code>enter</code> for all of the initial state's hierarchy,
+ * starting at its eldest parent. The calls to enter will be done in the context
+ * of the StateMachine's Handler, not in the context of the call to start, and they
+ * will be invoked before any messages are processed. For example, given the simple
+ * state machine below, mP1.enter will be invoked and then mS1.enter. Finally,
+ * messages sent to the state machine will be processed by the current state;
+ * in our simple state machine below that would initially be mS1.processMessage.</p>
+ <pre>
+ mP1
+ / \
+ mS2 mS1 ----> initial state
+ </pre>
+ * <p>After the state machine is created and started, messages are sent to a state
+ * machine using <code>sendMessage</code> and the messages are created using
+ * <code>obtainMessage</code>. When the state machine receives a message the
+ * current state's <code>processMessage</code> is invoked. In the above example
+ * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code>
+ * to change the current state to a new state.</p>
+ *
+ * <p>Each state in the state machine may have a zero or one parent states. If
+ * a child state is unable to handle a message it may have the message processed
+ * by its parent by returning false or NOT_HANDLED. If a message is not handled by
+ * a child state or any of its ancestors, <code>unhandledMessage</code> will be invoked
+ * to give one last chance for the state machine to process the message.</p>
+ *
+ * <p>When all processing is completed a state machine may choose to call
+ * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code>
+ * returns the state machine will transfer to an internal <code>HaltingState</code>
+ * and invoke <code>halting</code>. Any message subsequently received by the state
+ * machine will cause <code>haltedProcessMessage</code> to be invoked.</p>
+ *
+ * <p>If it is desirable to completely stop the state machine call <code>quit</code> or
+ * <code>quitNow</code>. These will call <code>exit</code> of the current state and its parents,
+ * call <code>onQuitting</code> and then exit Thread/Loopers.</p>
+ *
+ * <p>In addition to <code>processMessage</code> each <code>State</code> has
+ * an <code>enter</code> method and <code>exit</code> method which may be overridden.</p>
+ *
+ * <p>Since the states are arranged in a hierarchy transitioning to a new state
+ * causes current states to be exited and new states to be entered. To determine
+ * the list of states to be entered/exited the common parent closest to
+ * the current state is found. We then exit from the current state and its
+ * parent's up to but not including the common parent state and then enter all
+ * of the new states below the common parent down to the destination state.
+ * If there is no common parent all states are exited and then the new states
+ * are entered.</p>
+ *
+ * <p>Two other methods that states can use are <code>deferMessage</code> and
+ * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends
+ * a message but places it on the front of the queue rather than the back. The
+ * <code>deferMessage</code> causes the message to be saved on a list until a
+ * transition is made to a new state. At which time all of the deferred messages
+ * will be put on the front of the state machine queue with the oldest message
+ * at the front. These will then be processed by the new current state before
+ * any other messages that are on the queue or might be added later. Both of
+ * these are protected and may only be invoked from within a state machine.</p>
+ *
+ * <p>To illustrate some of these properties we'll use state machine with an 8
+ * state hierarchy:</p>
+ <pre>
+ mP0
+ / \
+ mP1 mS0
+ / \
+ mS2 mS1
+ / \ \
+ mS3 mS4 mS5 ---> initial state
+ </pre>
+ * <p>After starting mS5 the list of active states is mP0, mP1, mS1 and mS5.
+ * So the order of calling processMessage when a message is received is mS5,
+ * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
+ * message by returning false or NOT_HANDLED.</p>
+ *
+ * <p>Now assume mS5.processMessage receives a message it can handle, and during
+ * the handling determines the machine should change states. It could call
+ * transitionTo(mS4) and return true or HANDLED. Immediately after returning from
+ * processMessage the state machine runtime will find the common parent,
+ * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then
+ * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
+ * when the next message is received mS4.processMessage will be invoked.</p>
+ *
+ * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine.
+ * It responds with "Hello World" being printed to the log for every message.</p>
+ <pre>
+ class HelloWorld extends StateMachine {
+ HelloWorld(String name) {
+ super(name);
+ addState(mState1);
+ setInitialState(mState1);
+ }
+
+ public static HelloWorld makeHelloWorld() {
+ HelloWorld hw = new HelloWorld("hw");
+ hw.start();
+ return hw;
+ }
+
+ class State1 extends State {
+ @Override public boolean processMessage(Message message) {
+ log("Hello World");
+ return HANDLED;
+ }
+ }
+ State1 mState1 = new State1();
+ }
+
+ void testHelloWorld() {
+ HelloWorld hw = makeHelloWorld();
+ hw.sendMessage(hw.obtainMessage());
+ }
+ </pre>
+ * <p>A more interesting state machine is one with four states
+ * with two independent parent states.</p>
+ <pre>
+ mP1 mP2
+ / \
+ mS2 mS1
+ </pre>
+ * <p>Here is a description of this state machine using pseudo code.</p>
+ <pre>
+ state mP1 {
+ enter { log("mP1.enter"); }
+ exit { log("mP1.exit"); }
+ on msg {
+ CMD_2 {
+ send(CMD_3);
+ defer(msg);
+ transitionTo(mS2);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ INITIAL
+ state mS1 parent mP1 {
+ enter { log("mS1.enter"); }
+ exit { log("mS1.exit"); }
+ on msg {
+ CMD_1 {
+ transitionTo(mS1);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ state mS2 parent mP1 {
+ enter { log("mS2.enter"); }
+ exit { log("mS2.exit"); }
+ on msg {
+ CMD_2 {
+ send(CMD_4);
+ return HANDLED;
+ }
+ CMD_3 {
+ defer(msg);
+ transitionTo(mP2);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ state mP2 {
+ enter {
+ log("mP2.enter");
+ send(CMD_5);
+ }
+ exit { log("mP2.exit"); }
+ on msg {
+ CMD_3, CMD_4 { return HANDLED; }
+ CMD_5 {
+ transitionTo(HaltingState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+ </pre>
+ * <p>The implementation is below and also in StateMachineTest:</p>
+ <pre>
+ class Hsm1 extends StateMachine {
+ public static final int CMD_1 = 1;
+ public static final int CMD_2 = 2;
+ public static final int CMD_3 = 3;
+ public static final int CMD_4 = 4;
+ public static final int CMD_5 = 5;
+
+ public static Hsm1 makeHsm1() {
+ log("makeHsm1 E");
+ Hsm1 sm = new Hsm1("hsm1");
+ sm.start();
+ log("makeHsm1 X");
+ return sm;
+ }
+
+ Hsm1(String name) {
+ super(name);
+ log("ctor E");
+
+ // Add states, use indentation to show hierarchy
+ addState(mP1);
+ addState(mS1, mP1);
+ addState(mS2, mP1);
+ addState(mP2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ log("ctor X");
+ }
+
+ class P1 extends State {
+ @Override public void enter() {
+ log("mP1.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ boolean retVal;
+ log("mP1.processMessage what=" + message.what);
+ switch(message.what) {
+ case CMD_2:
+ // CMD_2 will arrive in mS2 before CMD_3
+ sendMessage(obtainMessage(CMD_3));
+ deferMessage(message);
+ transitionTo(mS2);
+ retVal = HANDLED;
+ break;
+ default:
+ // Any message we don't understand in this state invokes unhandledMessage
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ @Override public void exit() {
+ log("mP1.exit");
+ }
+ }
+
+ class S1 extends State {
+ @Override public void enter() {
+ log("mS1.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ log("S1.processMessage what=" + message.what);
+ if (message.what == CMD_1) {
+ // Transition to ourself to show that enter/exit is called
+ transitionTo(mS1);
+ return HANDLED;
+ } else {
+ // Let parent process all other messages
+ return NOT_HANDLED;
+ }
+ }
+ @Override public void exit() {
+ log("mS1.exit");
+ }
+ }
+
+ class S2 extends State {
+ @Override public void enter() {
+ log("mS2.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ boolean retVal;
+ log("mS2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_2):
+ sendMessage(obtainMessage(CMD_4));
+ retVal = HANDLED;
+ break;
+ case(CMD_3):
+ deferMessage(message);
+ transitionTo(mP2);
+ retVal = HANDLED;
+ break;
+ default:
+ retVal = NOT_HANDLED;
+ break;
+ }
+ return retVal;
+ }
+ @Override public void exit() {
+ log("mS2.exit");
+ }
+ }
+
+ class P2 extends State {
+ @Override public void enter() {
+ log("mP2.enter");
+ sendMessage(obtainMessage(CMD_5));
+ }
+ @Override public boolean processMessage(Message message) {
+ log("P2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_3):
+ break;
+ case(CMD_4):
+ break;
+ case(CMD_5):
+ transitionToHaltingState();
+ break;
+ }
+ return HANDLED;
+ }
+ @Override public void exit() {
+ log("mP2.exit");
+ }
+ }
+
+ @Override
+ void onHalting() {
+ log("halting");
+ synchronized (this) {
+ this.notifyAll();
+ }
+ }
+
+ P1 mP1 = new P1();
+ S1 mS1 = new S1();
+ S2 mS2 = new S2();
+ P2 mP2 = new P2();
+ }
+ </pre>
+ * <p>If this is executed by sending two messages CMD_1 and CMD_2
+ * (Note the synchronize is only needed because we use hsm.wait())</p>
+ <pre>
+ Hsm1 hsm = makeHsm1();
+ synchronize(hsm) {
+ hsm.sendMessage(obtainMessage(hsm.CMD_1));
+ hsm.sendMessage(obtainMessage(hsm.CMD_2));
+ try {
+ // wait for the messages to be handled
+ hsm.wait();
+ } catch (InterruptedException e) {
+ loge("exception while waiting " + e.getMessage());
+ }
+ }
+ </pre>
+ * <p>The output is:</p>
+ <pre>
+ D/hsm1 ( 1999): makeHsm1 E
+ D/hsm1 ( 1999): ctor E
+ D/hsm1 ( 1999): ctor X
+ D/hsm1 ( 1999): mP1.enter
+ D/hsm1 ( 1999): mS1.enter
+ D/hsm1 ( 1999): makeHsm1 X
+ D/hsm1 ( 1999): mS1.processMessage what=1
+ D/hsm1 ( 1999): mS1.exit
+ D/hsm1 ( 1999): mS1.enter
+ D/hsm1 ( 1999): mS1.processMessage what=2
+ D/hsm1 ( 1999): mP1.processMessage what=2
+ D/hsm1 ( 1999): mS1.exit
+ D/hsm1 ( 1999): mS2.enter
+ D/hsm1 ( 1999): mS2.processMessage what=2
+ D/hsm1 ( 1999): mS2.processMessage what=3
+ D/hsm1 ( 1999): mS2.exit
+ D/hsm1 ( 1999): mP1.exit
+ D/hsm1 ( 1999): mP2.enter
+ D/hsm1 ( 1999): mP2.processMessage what=3
+ D/hsm1 ( 1999): mP2.processMessage what=4
+ D/hsm1 ( 1999): mP2.processMessage what=5
+ D/hsm1 ( 1999): mP2.exit
+ D/hsm1 ( 1999): halting
+ </pre>
+ */
+public class StateMachine {
+ // Name of the state machine and used as logging tag
+ private String mName;
+
+ /** Message.what value when quitting */
+ private static final int SM_QUIT_CMD = -1;
+
+ /** Message.what value when initializing */
+ private static final int SM_INIT_CMD = -2;
+
+ /**
+ * Convenience constant that maybe returned by processMessage
+ * to indicate the message was processed and is not to be
+ * processed by parent states
+ */
+ public static final boolean HANDLED = true;
+
+ /**
+ * Convenience constant that maybe returned by processMessage
+ * to indicate the message was NOT processed and is to be
+ * processed by parent states
+ */
+ public static final boolean NOT_HANDLED = false;
+
+ /**
+ * StateMachine logging record.
+ */
+ public static class LogRec {
+ private StateMachine mSm;
+ private long mTime;
+ private int mWhat;
+ private String mInfo;
+ private IState mState;
+ private IState mOrgState;
+ private IState mDstState;
+
+ /**
+ * Constructor
+ *
+ * @param msg
+ * @param state the state which handled the message
+ * @param orgState is the first state the received the message but
+ * did not processes the message.
+ * @param transToState is the state that was transitioned to after the message was
+ * processed.
+ */
+ LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState,
+ IState transToState) {
+ update(sm, msg, info, state, orgState, transToState);
+ }
+
+ /**
+ * Update the information in the record.
+ * @param state that handled the message
+ * @param orgState is the first state the received the message
+ * @param dstState is the state that was the transition target when logging
+ */
+ public void update(StateMachine sm, Message msg, String info, IState state, IState orgState,
+ IState dstState) {
+ mSm = sm;
+ mTime = System.currentTimeMillis();
+ mWhat = (msg != null) ? msg.what : 0;
+ mInfo = info;
+ mState = state;
+ mOrgState = orgState;
+ mDstState = dstState;
+ }
+
+ /**
+ * @return time stamp
+ */
+ public long getTime() {
+ return mTime;
+ }
+
+ /**
+ * @return msg.what
+ */
+ public long getWhat() {
+ return mWhat;
+ }
+
+ /**
+ * @return the command that was executing
+ */
+ public String getInfo() {
+ return mInfo;
+ }
+
+ /**
+ * @return the state that handled this message
+ */
+ public IState getState() {
+ return mState;
+ }
+
+ /**
+ * @return the state destination state if a transition is occurring or null if none.
+ */
+ public IState getDestState() {
+ return mDstState;
+ }
+
+ /**
+ * @return the original state that received the message.
+ */
+ public IState getOriginalState() {
+ return mOrgState;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("time=");
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(mTime);
+ sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
+ sb.append(" processed=");
+ sb.append(mState == null ? "<null>" : mState.getName());
+ sb.append(" org=");
+ sb.append(mOrgState == null ? "<null>" : mOrgState.getName());
+ sb.append(" dest=");
+ sb.append(mDstState == null ? "<null>" : mDstState.getName());
+ sb.append(" what=");
+ String what = mSm != null ? mSm.getWhatToString(mWhat) : "";
+ if (TextUtils.isEmpty(what)) {
+ sb.append(mWhat);
+ sb.append("(0x");
+ sb.append(Integer.toHexString(mWhat));
+ sb.append(")");
+ } else {
+ sb.append(what);
+ }
+ if (!TextUtils.isEmpty(mInfo)) {
+ sb.append(" ");
+ sb.append(mInfo);
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * A list of log records including messages recently processed by the state machine.
+ *
+ * The class maintains a list of log records including messages
+ * recently processed. The list is finite and may be set in the
+ * constructor or by calling setSize. The public interface also
+ * includes size which returns the number of recent records,
+ * count which is the number of records processed since the
+ * the last setSize, get which returns a record and
+ * add which adds a record.
+ */
+ private static class LogRecords {
+
+ private static final int DEFAULT_SIZE = 20;
+
+ private Vector<LogRec> mLogRecVector = new Vector<LogRec>();
+ private int mMaxSize = DEFAULT_SIZE;
+ private int mOldestIndex = 0;
+ private int mCount = 0;
+ private boolean mLogOnlyTransitions = false;
+
+ /**
+ * private constructor use add
+ */
+ private LogRecords() {
+ }
+
+ /**
+ * Set size of messages to maintain and clears all current records.
+ *
+ * @param maxSize number of records to maintain at anyone time.
+ */
+ synchronized void setSize(int maxSize) {
+ // TODO: once b/28217358 is fixed, add unit tests to verify that these variables are
+ // cleared after calling this method, and that subsequent calls to get() function as
+ // expected.
+ mMaxSize = maxSize;
+ mOldestIndex = 0;
+ mCount = 0;
+ mLogRecVector.clear();
+ }
+
+ synchronized void setLogOnlyTransitions(boolean enable) {
+ mLogOnlyTransitions = enable;
+ }
+
+ synchronized boolean logOnlyTransitions() {
+ return mLogOnlyTransitions;
+ }
+
+ /**
+ * @return the number of recent records.
+ */
+ synchronized int size() {
+ return mLogRecVector.size();
+ }
+
+ /**
+ * @return the total number of records processed since size was set.
+ */
+ synchronized int count() {
+ return mCount;
+ }
+
+ /**
+ * Clear the list of records.
+ */
+ synchronized void cleanup() {
+ mLogRecVector.clear();
+ }
+
+ /**
+ * @return the information on a particular record. 0 is the oldest
+ * record and size()-1 is the newest record. If the index is to
+ * large null is returned.
+ */
+ synchronized LogRec get(int index) {
+ int nextIndex = mOldestIndex + index;
+ if (nextIndex >= mMaxSize) {
+ nextIndex -= mMaxSize;
+ }
+ if (nextIndex >= size()) {
+ return null;
+ } else {
+ return mLogRecVector.get(nextIndex);
+ }
+ }
+
+ /**
+ * Add a processed message.
+ *
+ * @param msg
+ * @param messageInfo to be stored
+ * @param state that handled the message
+ * @param orgState is the first state the received the message but
+ * did not processes the message.
+ * @param transToState is the state that was transitioned to after the message was
+ * processed.
+ *
+ */
+ synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state,
+ IState orgState, IState transToState) {
+ mCount += 1;
+ if (mLogRecVector.size() < mMaxSize) {
+ mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState));
+ } else {
+ LogRec pmi = mLogRecVector.get(mOldestIndex);
+ mOldestIndex += 1;
+ if (mOldestIndex >= mMaxSize) {
+ mOldestIndex = 0;
+ }
+ pmi.update(sm, msg, messageInfo, state, orgState, transToState);
+ }
+ }
+ }
+
+ private static class SmHandler extends Handler {
+
+ /** true if StateMachine has quit */
+ private boolean mHasQuit = false;
+
+ /** The debug flag */
+ private boolean mDbg = false;
+
+ /** The SmHandler object, identifies that message is internal */
+ private static final Object mSmHandlerObj = new Object();
+
+ /** The current message */
+ private Message mMsg;
+
+ /** A list of log records including messages this state machine has processed */
+ private LogRecords mLogRecords = new LogRecords();
+
+ /** true if construction of the state machine has not been completed */
+ private boolean mIsConstructionCompleted;
+
+ /** Stack used to manage the current hierarchy of states */
+ private StateInfo mStateStack[];
+
+ /** Top of mStateStack */
+ private int mStateStackTopIndex = -1;
+
+ /** A temporary stack used to manage the state stack */
+ private StateInfo mTempStateStack[];
+
+ /** The top of the mTempStateStack */
+ private int mTempStateStackCount;
+
+ /** State used when state machine is halted */
+ private HaltingState mHaltingState = new HaltingState();
+
+ /** State used when state machine is quitting */
+ private QuittingState mQuittingState = new QuittingState();
+
+ /** Reference to the StateMachine */
+ private StateMachine mSm;
+
+ /**
+ * Information about a state.
+ * Used to maintain the hierarchy.
+ */
+ private class StateInfo {
+ /** The state */
+ State state;
+
+ /** The parent of this state, null if there is no parent */
+ StateInfo parentStateInfo;
+
+ /** True when the state has been entered and on the stack */
+ boolean active;
+
+ /**
+ * Convert StateInfo to string
+ */
+ @Override
+ public String toString() {
+ return "state=" + state.getName() + ",active=" + active + ",parent="
+ + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
+ }
+ }
+
+ /** The map of all of the states in the state machine */
+ private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
+
+ /** The initial state that will process the first message */
+ private State mInitialState;
+
+ /** The destination state when transitionTo has been invoked */
+ private State mDestState;
+
+ /**
+ * Indicates if a transition is in progress
+ *
+ * This will be true for all calls of State.exit and all calls of State.enter except for the
+ * last enter call for the current destination state.
+ */
+ private boolean mTransitionInProgress = false;
+
+ /** The list of deferred messages */
+ private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
+
+ /**
+ * State entered when transitionToHaltingState is called.
+ */
+ private class HaltingState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ mSm.haltedProcessMessage(msg);
+ return true;
+ }
+ }
+
+ /**
+ * State entered when a valid quit message is handled.
+ */
+ private class QuittingState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ return NOT_HANDLED;
+ }
+ }
+
+ /**
+ * Handle messages sent to the state machine by calling
+ * the current state's processMessage. It also handles
+ * the enter/exit calls and placing any deferred messages
+ * back onto the queue when transitioning to a new state.
+ */
+ @Override
+ public final void handleMessage(Message msg) {
+ if (!mHasQuit) {
+ if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
+ mSm.onPreHandleMessage(msg);
+ }
+
+ if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
+
+ /** Save the current message */
+ mMsg = msg;
+
+ /** State that processed the message */
+ State msgProcessedState = null;
+ if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
+ /** Normal path */
+ msgProcessedState = processMsg(msg);
+ } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
+ && (mMsg.obj == mSmHandlerObj)) {
+ /** Initial one time path. */
+ mIsConstructionCompleted = true;
+ invokeEnterMethods(0);
+ } else {
+ throw new RuntimeException("StateMachine.handleMessage: "
+ + "The start method not called, received msg: " + msg);
+ }
+ performTransitions(msgProcessedState, msg);
+
+ // We need to check if mSm == null here as we could be quitting.
+ if (mDbg && mSm != null) mSm.log("handleMessage: X");
+
+ if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
+ mSm.onPostHandleMessage(msg);
+ }
+ }
+ }
+
+ /**
+ * Do any transitions
+ * @param msgProcessedState is the state that processed the message
+ */
+ private void performTransitions(State msgProcessedState, Message msg) {
+ /**
+ * If transitionTo has been called, exit and then enter
+ * the appropriate states. We loop on this to allow
+ * enter and exit methods to use transitionTo.
+ */
+ State orgState = mStateStack[mStateStackTopIndex].state;
+
+ /**
+ * Record whether message needs to be logged before we transition and
+ * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
+ * always set msg.obj to the handler.
+ */
+ boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);
+
+ if (mLogRecords.logOnlyTransitions()) {
+ /** Record only if there is a transition */
+ if (mDestState != null) {
+ mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
+ orgState, mDestState);
+ }
+ } else if (recordLogMsg) {
+ /** Record message */
+ mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
+ mDestState);
+ }
+
+ State destState = mDestState;
+ if (destState != null) {
+ /**
+ * Process the transitions including transitions in the enter/exit methods
+ */
+ while (true) {
+ if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
+
+ /**
+ * Determine the states to exit and enter and return the
+ * common ancestor state of the enter/exit states. Then
+ * invoke the exit methods then the enter methods.
+ */
+ StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
+ // flag is cleared in invokeEnterMethods before entering the target state
+ mTransitionInProgress = true;
+ invokeExitMethods(commonStateInfo);
+ int stateStackEnteringIndex = moveTempStateStackToStateStack();
+ invokeEnterMethods(stateStackEnteringIndex);
+
+ /**
+ * Since we have transitioned to a new state we need to have
+ * any deferred messages moved to the front of the message queue
+ * so they will be processed before any other messages in the
+ * message queue.
+ */
+ moveDeferredMessageAtFrontOfQueue();
+
+ if (destState != mDestState) {
+ // A new mDestState so continue looping
+ destState = mDestState;
+ } else {
+ // No change in mDestState so we're done
+ break;
+ }
+ }
+ mDestState = null;
+ }
+
+ /**
+ * After processing all transitions check and
+ * see if the last transition was to quit or halt.
+ */
+ if (destState != null) {
+ if (destState == mQuittingState) {
+ /**
+ * Call onQuitting to let subclasses cleanup.
+ */
+ mSm.onQuitting();
+ cleanupAfterQuitting();
+ } else if (destState == mHaltingState) {
+ /**
+ * Call onHalting() if we've transitioned to the halting
+ * state. All subsequent messages will be processed in
+ * in the halting state which invokes haltedProcessMessage(msg);
+ */
+ mSm.onHalting();
+ }
+ }
+ }
+
+ /**
+ * Cleanup all the static variables and the looper after the SM has been quit.
+ */
+ private final void cleanupAfterQuitting() {
+ if (mSm.mSmThread != null) {
+ // If we made the thread then quit looper which stops the thread.
+ getLooper().quit();
+ mSm.mSmThread = null;
+ }
+
+ mSm.mSmHandler = null;
+ mSm = null;
+ mMsg = null;
+ mLogRecords.cleanup();
+ mStateStack = null;
+ mTempStateStack = null;
+ mStateInfo.clear();
+ mInitialState = null;
+ mDestState = null;
+ mDeferredMessages.clear();
+ mHasQuit = true;
+ }
+
+ /**
+ * Complete the construction of the state machine.
+ */
+ private final void completeConstruction() {
+ if (mDbg) mSm.log("completeConstruction: E");
+
+ /**
+ * Determine the maximum depth of the state hierarchy
+ * so we can allocate the state stacks.
+ */
+ int maxDepth = 0;
+ for (StateInfo si : mStateInfo.values()) {
+ int depth = 0;
+ for (StateInfo i = si; i != null; depth++) {
+ i = i.parentStateInfo;
+ }
+ if (maxDepth < depth) {
+ maxDepth = depth;
+ }
+ }
+ if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
+
+ mStateStack = new StateInfo[maxDepth];
+ mTempStateStack = new StateInfo[maxDepth];
+ setupInitialStateStack();
+
+ /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
+ sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
+
+ if (mDbg) mSm.log("completeConstruction: X");
+ }
+
+ /**
+ * Process the message. If the current state doesn't handle
+ * it, call the states parent and so on. If it is never handled then
+ * call the state machines unhandledMessage method.
+ * @return the state that processed the message
+ */
+ private final State processMsg(Message msg) {
+ StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
+ if (mDbg) {
+ mSm.log("processMsg: " + curStateInfo.state.getName());
+ }
+
+ if (isQuit(msg)) {
+ transitionTo(mQuittingState);
+ } else {
+ while (!curStateInfo.state.processMessage(msg)) {
+ /**
+ * Not processed
+ */
+ curStateInfo = curStateInfo.parentStateInfo;
+ if (curStateInfo == null) {
+ /**
+ * No parents left so it's not handled
+ */
+ mSm.unhandledMessage(msg);
+ break;
+ }
+ if (mDbg) {
+ mSm.log("processMsg: " + curStateInfo.state.getName());
+ }
+ }
+ }
+ return (curStateInfo != null) ? curStateInfo.state : null;
+ }
+
+ /**
+ * Call the exit method for each state from the top of stack
+ * up to the common ancestor state.
+ */
+ private final void invokeExitMethods(StateInfo commonStateInfo) {
+ while ((mStateStackTopIndex >= 0)
+ && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
+ State curState = mStateStack[mStateStackTopIndex].state;
+ if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
+ curState.exit();
+ mStateStack[mStateStackTopIndex].active = false;
+ mStateStackTopIndex -= 1;
+ }
+ }
+
+ /**
+ * Invoke the enter method starting at the entering index to top of state stack
+ */
+ private final void invokeEnterMethods(int stateStackEnteringIndex) {
+ for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
+ if (stateStackEnteringIndex == mStateStackTopIndex) {
+ // Last enter state for transition
+ mTransitionInProgress = false;
+ }
+ if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
+ mStateStack[i].state.enter();
+ mStateStack[i].active = true;
+ }
+ mTransitionInProgress = false; // ensure flag set to false if no methods called
+ }
+
+ /**
+ * Move the deferred message to the front of the message queue.
+ */
+ private final void moveDeferredMessageAtFrontOfQueue() {
+ /**
+ * The oldest messages on the deferred list must be at
+ * the front of the queue so start at the back, which
+ * as the most resent message and end with the oldest
+ * messages at the front of the queue.
+ */
+ for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
+ Message curMsg = mDeferredMessages.get(i);
+ if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
+ sendMessageAtFrontOfQueue(curMsg);
+ }
+ mDeferredMessages.clear();
+ }
+
+ /**
+ * Move the contents of the temporary stack to the state stack
+ * reversing the order of the items on the temporary stack as
+ * they are moved.
+ *
+ * @return index into mStateStack where entering needs to start
+ */
+ private final int moveTempStateStackToStateStack() {
+ int startingIndex = mStateStackTopIndex + 1;
+ int i = mTempStateStackCount - 1;
+ int j = startingIndex;
+ while (i >= 0) {
+ if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
+ mStateStack[j] = mTempStateStack[i];
+ j += 1;
+ i -= 1;
+ }
+
+ mStateStackTopIndex = j - 1;
+ if (mDbg) {
+ mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
+ + ",startingIndex=" + startingIndex + ",Top="
+ + mStateStack[mStateStackTopIndex].state.getName());
+ }
+ return startingIndex;
+ }
+
+ /**
+ * Setup the mTempStateStack with the states we are going to enter.
+ *
+ * This is found by searching up the destState's ancestors for a
+ * state that is already active i.e. StateInfo.active == true.
+ * The destStae and all of its inactive parents will be on the
+ * TempStateStack as the list of states to enter.
+ *
+ * @return StateInfo of the common ancestor for the destState and
+ * current state or null if there is no common parent.
+ */
+ private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
+ /**
+ * Search up the parent list of the destination state for an active
+ * state. Use a do while() loop as the destState must always be entered
+ * even if it is active. This can happen if we are exiting/entering
+ * the current state.
+ */
+ mTempStateStackCount = 0;
+ StateInfo curStateInfo = mStateInfo.get(destState);
+ do {
+ mTempStateStack[mTempStateStackCount++] = curStateInfo;
+ curStateInfo = curStateInfo.parentStateInfo;
+ } while ((curStateInfo != null) && !curStateInfo.active);
+
+ if (mDbg) {
+ mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
+ + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
+ }
+ return curStateInfo;
+ }
+
+ /**
+ * Initialize StateStack to mInitialState.
+ */
+ private final void setupInitialStateStack() {
+ if (mDbg) {
+ mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
+ }
+
+ StateInfo curStateInfo = mStateInfo.get(mInitialState);
+ for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
+ mTempStateStack[mTempStateStackCount] = curStateInfo;
+ curStateInfo = curStateInfo.parentStateInfo;
+ }
+
+ // Empty the StateStack
+ mStateStackTopIndex = -1;
+
+ moveTempStateStackToStateStack();
+ }
+
+ /**
+ * @return current message
+ */
+ private final Message getCurrentMessage() {
+ return mMsg;
+ }
+
+ /**
+ * @return current state
+ */
+ private final IState getCurrentState() {
+ return mStateStack[mStateStackTopIndex].state;
+ }
+
+ /**
+ * Add a new state to the state machine. Bottom up addition
+ * of states is allowed but the same state may only exist
+ * in one hierarchy.
+ *
+ * @param state the state to add
+ * @param parent the parent of state
+ * @return stateInfo for this state
+ */
+ private final StateInfo addState(State state, State parent) {
+ if (mDbg) {
+ mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
+ + ((parent == null) ? "" : parent.getName()));
+ }
+ StateInfo parentStateInfo = null;
+ if (parent != null) {
+ parentStateInfo = mStateInfo.get(parent);
+ if (parentStateInfo == null) {
+ // Recursively add our parent as it's not been added yet.
+ parentStateInfo = addState(parent, null);
+ }
+ }
+ StateInfo stateInfo = mStateInfo.get(state);
+ if (stateInfo == null) {
+ stateInfo = new StateInfo();
+ mStateInfo.put(state, stateInfo);
+ }
+
+ // Validate that we aren't adding the same state in two different hierarchies.
+ if ((stateInfo.parentStateInfo != null)
+ && (stateInfo.parentStateInfo != parentStateInfo)) {
+ throw new RuntimeException("state already added");
+ }
+ stateInfo.state = state;
+ stateInfo.parentStateInfo = parentStateInfo;
+ stateInfo.active = false;
+ if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
+ return stateInfo;
+ }
+
+ /**
+ * Remove a state from the state machine. Will not remove the state if it is currently
+ * active or if it has any children in the hierarchy.
+ * @param state the state to remove
+ */
+ private void removeState(State state) {
+ StateInfo stateInfo = mStateInfo.get(state);
+ if (stateInfo == null || stateInfo.active) {
+ return;
+ }
+ boolean isParent = mStateInfo.values().stream()
+ .filter(si -> si.parentStateInfo == stateInfo)
+ .findAny()
+ .isPresent();
+ if (isParent) {
+ return;
+ }
+ mStateInfo.remove(state);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param looper for dispatching messages
+ * @param sm the hierarchical state machine
+ */
+ private SmHandler(Looper looper, StateMachine sm) {
+ super(looper);
+ mSm = sm;
+
+ addState(mHaltingState, null);
+ addState(mQuittingState, null);
+ }
+
+ /** @see StateMachine#setInitialState(State) */
+ private final void setInitialState(State initialState) {
+ if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
+ mInitialState = initialState;
+ }
+
+ /** @see StateMachine#transitionTo(IState) */
+ private final void transitionTo(IState destState) {
+ if (mTransitionInProgress) {
+ Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +
+ mDestState + ", new target state=" + destState);
+ }
+ mDestState = (State) destState;
+ if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
+ }
+
+ /** @see StateMachine#deferMessage(Message) */
+ private final void deferMessage(Message msg) {
+ if (mDbg) mSm.log("deferMessage: msg=" + msg.what);
+
+ /* Copy the "msg" to "newMsg" as "msg" will be recycled */
+ Message newMsg = obtainMessage();
+ newMsg.copyFrom(msg);
+
+ mDeferredMessages.add(newMsg);
+ }
+
+ /** @see StateMachine#quit() */
+ private final void quit() {
+ if (mDbg) mSm.log("quit:");
+ sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
+ }
+
+ /** @see StateMachine#quitNow() */
+ private final void quitNow() {
+ if (mDbg) mSm.log("quitNow:");
+ sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
+ }
+
+ /** Validate that the message was sent by quit or quitNow. */
+ private final boolean isQuit(Message msg) {
+ return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);
+ }
+
+ /** @see StateMachine#isDbg() */
+ private final boolean isDbg() {
+ return mDbg;
+ }
+
+ /** @see StateMachine#setDbg(boolean) */
+ private final void setDbg(boolean dbg) {
+ mDbg = dbg;
+ }
+
+ }
+
+ private SmHandler mSmHandler;
+ private HandlerThread mSmThread;
+
+ /**
+ * Initialize.
+ *
+ * @param looper for this state machine
+ * @param name of the state machine
+ */
+ private void initStateMachine(String name, Looper looper) {
+ mName = name;
+ mSmHandler = new SmHandler(looper, this);
+ }
+
+ /**
+ * Constructor creates a StateMachine with its own thread.
+ *
+ * @param name of the state machine
+ */
+ protected StateMachine(String name) {
+ mSmThread = new HandlerThread(name);
+ mSmThread.start();
+ Looper looper = mSmThread.getLooper();
+
+ initStateMachine(name, looper);
+ }
+
+ /**
+ * Constructor creates a StateMachine using the looper.
+ *
+ * @param name of the state machine
+ */
+ protected StateMachine(String name, Looper looper) {
+ initStateMachine(name, looper);
+ }
+
+ /**
+ * Constructor creates a StateMachine using the handler.
+ *
+ * @param name of the state machine
+ */
+ protected StateMachine(String name, Handler handler) {
+ initStateMachine(name, handler.getLooper());
+ }
+
+ /**
+ * Notifies subclass that the StateMachine handler is about to process the Message msg
+ * @param msg The message that is being handled
+ */
+ protected void onPreHandleMessage(Message msg) {
+ }
+
+ /**
+ * Notifies subclass that the StateMachine handler has finished processing the Message msg and
+ * has possibly transitioned to a new state.
+ * @param msg The message that is being handled
+ */
+ protected void onPostHandleMessage(Message msg) {
+ }
+
+ /**
+ * Add a new state to the state machine
+ * @param state the state to add
+ * @param parent the parent of state
+ */
+ public final void addState(State state, State parent) {
+ mSmHandler.addState(state, parent);
+ }
+
+ /**
+ * Add a new state to the state machine, parent will be null
+ * @param state to add
+ */
+ public final void addState(State state) {
+ mSmHandler.addState(state, null);
+ }
+
+ /**
+ * Removes a state from the state machine, unless it is currently active or if it has children.
+ * @param state state to remove
+ */
+ public final void removeState(State state) {
+ mSmHandler.removeState(state);
+ }
+
+ /**
+ * Set the initial state. This must be invoked before
+ * and messages are sent to the state machine.
+ *
+ * @param initialState is the state which will receive the first message.
+ */
+ public final void setInitialState(State initialState) {
+ mSmHandler.setInitialState(initialState);
+ }
+
+ /**
+ * @return current message
+ */
+ public final Message getCurrentMessage() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.getCurrentMessage();
+ }
+
+ /**
+ * @return current state
+ */
+ public final IState getCurrentState() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.getCurrentState();
+ }
+
+ /**
+ * transition to destination state. Upon returning
+ * from processMessage the current state's exit will
+ * be executed and upon the next message arriving
+ * destState.enter will be invoked.
+ *
+ * this function can also be called inside the enter function of the
+ * previous transition target, but the behavior is undefined when it is
+ * called mid-way through a previous transition (for example, calling this
+ * in the enter() routine of a intermediate node when the current transition
+ * target is one of the nodes descendants).
+ *
+ * @param destState will be the state that receives the next message.
+ */
+ public final void transitionTo(IState destState) {
+ mSmHandler.transitionTo(destState);
+ }
+
+ /**
+ * transition to halt state. Upon returning
+ * from processMessage we will exit all current
+ * states, execute the onHalting() method and then
+ * for all subsequent messages haltedProcessMessage
+ * will be called.
+ */
+ public final void transitionToHaltingState() {
+ mSmHandler.transitionTo(mSmHandler.mHaltingState);
+ }
+
+ /**
+ * Defer this message until next state transition.
+ * Upon transitioning all deferred messages will be
+ * placed on the queue and reprocessed in the original
+ * order. (i.e. The next state the oldest messages will
+ * be processed first)
+ *
+ * @param msg is deferred until the next transition.
+ */
+ public final void deferMessage(Message msg) {
+ mSmHandler.deferMessage(msg);
+ }
+
+ /**
+ * Called when message wasn't handled
+ *
+ * @param msg that couldn't be handled.
+ */
+ protected void unhandledMessage(Message msg) {
+ if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);
+ }
+
+ /**
+ * Called for any message that is received after
+ * transitionToHalting is called.
+ */
+ protected void haltedProcessMessage(Message msg) {
+ }
+
+ /**
+ * This will be called once after handling a message that called
+ * transitionToHalting. All subsequent messages will invoke
+ * {@link StateMachine#haltedProcessMessage(Message)}
+ */
+ protected void onHalting() {
+ }
+
+ /**
+ * This will be called once after a quit message that was NOT handled by
+ * the derived StateMachine. The StateMachine will stop and any subsequent messages will be
+ * ignored. In addition, if this StateMachine created the thread, the thread will
+ * be stopped after this method returns.
+ */
+ protected void onQuitting() {
+ }
+
+ /**
+ * @return the name
+ */
+ public final String getName() {
+ return mName;
+ }
+
+ /**
+ * Set number of log records to maintain and clears all current records.
+ *
+ * @param maxSize number of messages to maintain at anyone time.
+ */
+ public final void setLogRecSize(int maxSize) {
+ mSmHandler.mLogRecords.setSize(maxSize);
+ }
+
+ /**
+ * Set to log only messages that cause a state transition
+ *
+ * @param enable {@code true} to enable, {@code false} to disable
+ */
+ public final void setLogOnlyTransitions(boolean enable) {
+ mSmHandler.mLogRecords.setLogOnlyTransitions(enable);
+ }
+
+ /**
+ * @return the number of log records currently readable
+ */
+ public final int getLogRecSize() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return 0;
+ return smh.mLogRecords.size();
+ }
+
+ /**
+ * @return the number of log records we can store
+ */
+ @VisibleForTesting
+ public final int getLogRecMaxSize() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return 0;
+ return smh.mLogRecords.mMaxSize;
+ }
+
+ /**
+ * @return the total number of records processed
+ */
+ public final int getLogRecCount() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return 0;
+ return smh.mLogRecords.count();
+ }
+
+ /**
+ * @return a log record, or null if index is out of range
+ */
+ public final LogRec getLogRec(int index) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.mLogRecords.get(index);
+ }
+
+ /**
+ * @return a copy of LogRecs as a collection
+ */
+ public final Collection<LogRec> copyLogRecs() {
+ Vector<LogRec> vlr = new Vector<LogRec>();
+ SmHandler smh = mSmHandler;
+ if (smh != null) {
+ for (LogRec lr : smh.mLogRecords.mLogRecVector) {
+ vlr.add(lr);
+ }
+ }
+ return vlr;
+ }
+
+ /**
+ * Add the string to LogRecords.
+ *
+ * @param string
+ */
+ public void addLogRec(String string) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+ smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(),
+ smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState);
+ }
+
+ /**
+ * @return true if msg should be saved in the log, default is true.
+ */
+ protected boolean recordLogRec(Message msg) {
+ return true;
+ }
+
+ /**
+ * Return a string to be logged by LogRec, default
+ * is an empty string. Override if additional information is desired.
+ *
+ * @param msg that was processed
+ * @return information to be logged as a String
+ */
+ protected String getLogRecString(Message msg) {
+ return "";
+ }
+
+ /**
+ * @return the string for msg.what
+ */
+ protected String getWhatToString(int what) {
+ return null;
+ }
+
+ /**
+ * @return Handler, maybe null if state machine has quit.
+ */
+ public final Handler getHandler() {
+ return mSmHandler;
+ }
+
+ /**
+ * Get a message and set Message.target state machine handler.
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
+ * @return A Message object from the global pool
+ */
+ public final Message obtainMessage() {
+ return Message.obtain(mSmHandler);
+ }
+
+ /**
+ * Get a message and set Message.target state machine handler, what.
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
+ * @param what is the assigned to Message.what.
+ * @return A Message object from the global pool
+ */
+ public final Message obtainMessage(int what) {
+ return Message.obtain(mSmHandler, what);
+ }
+
+ /**
+ * Get a message and set Message.target state machine handler,
+ * what and obj.
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
+ * @param what is the assigned to Message.what.
+ * @param obj is assigned to Message.obj.
+ * @return A Message object from the global pool
+ */
+ public final Message obtainMessage(int what, Object obj) {
+ return Message.obtain(mSmHandler, what, obj);
+ }
+
+ /**
+ * Get a message and set Message.target state machine handler,
+ * what, arg1 and arg2
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
+ * @param what is assigned to Message.what
+ * @param arg1 is assigned to Message.arg1
+ * @return A Message object from the global pool
+ */
+ public final Message obtainMessage(int what, int arg1) {
+ // use this obtain so we don't match the obtain(h, what, Object) method
+ return Message.obtain(mSmHandler, what, arg1, 0);
+ }
+
+ /**
+ * Get a message and set Message.target state machine handler,
+ * what, arg1 and arg2
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
+ * @param what is assigned to Message.what
+ * @param arg1 is assigned to Message.arg1
+ * @param arg2 is assigned to Message.arg2
+ * @return A Message object from the global pool
+ */
+ public final Message obtainMessage(int what, int arg1, int arg2) {
+ return Message.obtain(mSmHandler, what, arg1, arg2);
+ }
+
+ /**
+ * Get a message and set Message.target state machine handler,
+ * what, arg1, arg2 and obj
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
+ * @param what is assigned to Message.what
+ * @param arg1 is assigned to Message.arg1
+ * @param arg2 is assigned to Message.arg2
+ * @param obj is assigned to Message.obj
+ * @return A Message object from the global pool
+ */
+ public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {
+ return Message.obtain(mSmHandler, what, arg1, arg2, obj);
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessage(int what) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(obtainMessage(what));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessage(int what, Object obj) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(obtainMessage(what, obj));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessage(int what, int arg1) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(obtainMessage(what, arg1));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessage(int what, int arg1, int arg2) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(obtainMessage(what, arg1, arg2));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessage(int what, int arg1, int arg2, Object obj) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(obtainMessage(what, arg1, arg2, obj));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessage(Message msg) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(msg);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessageDelayed(int what, long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(obtainMessage(what), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessageDelayed(int what, Object obj, long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessageDelayed(int what, int arg1, long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
+ long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(obtainMessage(what, arg1, arg2, obj), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public void sendMessageDelayed(Message msg, long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(msg, delayMillis);
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what));
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what, int arg1) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1));
+ }
+
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2));
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2, obj));
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(Message msg) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(msg);
+ }
+
+ /**
+ * Removes a message from the message queue.
+ * Protected, may only be called by instances of StateMachine.
+ */
+ protected final void removeMessages(int what) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.removeMessages(what);
+ }
+
+ /**
+ * Removes a message from the deferred messages queue.
+ */
+ protected final void removeDeferredMessages(int what) {
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ Iterator<Message> iterator = smh.mDeferredMessages.iterator();
+ while (iterator.hasNext()) {
+ Message msg = iterator.next();
+ if (msg.what == what) iterator.remove();
+ }
+ }
+
+ /**
+ * Check if there are any pending messages with code 'what' in deferred messages queue.
+ */
+ protected final boolean hasDeferredMessages(int what) {
+ SmHandler smh = mSmHandler;
+ if (smh == null) return false;
+
+ Iterator<Message> iterator = smh.mDeferredMessages.iterator();
+ while (iterator.hasNext()) {
+ Message msg = iterator.next();
+ if (msg.what == what) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if there are any pending posts of messages with code 'what' in
+ * the message queue. This does NOT check messages in deferred message queue.
+ */
+ protected final boolean hasMessages(int what) {
+ SmHandler smh = mSmHandler;
+ if (smh == null) return false;
+
+ return smh.hasMessages(what);
+ }
+
+ /**
+ * Validate that the message was sent by
+ * {@link StateMachine#quit} or {@link StateMachine#quitNow}.
+ */
+ protected final boolean isQuit(Message msg) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return msg.what == SM_QUIT_CMD;
+
+ return smh.isQuit(msg);
+ }
+
+ /**
+ * Quit the state machine after all currently queued up messages are processed.
+ */
+ public final void quit() {
+ // mSmHandler can be null if the state machine is already stopped.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.quit();
+ }
+
+ /**
+ * Quit the state machine immediately all currently queued messages will be discarded.
+ */
+ public final void quitNow() {
+ // mSmHandler can be null if the state machine is already stopped.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.quitNow();
+ }
+
+ /**
+ * @return if debugging is enabled
+ */
+ public boolean isDbg() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return false;
+
+ return smh.isDbg();
+ }
+
+ /**
+ * Set debug enable/disabled.
+ *
+ * @param dbg is true to enable debugging.
+ */
+ public void setDbg(boolean dbg) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.setDbg(dbg);
+ }
+
+ /**
+ * Start the state machine.
+ */
+ public void start() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ /** Send the complete construction message */
+ smh.completeConstruction();
+ }
+
+ /**
+ * Dump the current state.
+ *
+ * @param fd
+ * @param pw
+ * @param args
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(getName() + ":");
+ pw.println(" total records=" + getLogRecCount());
+ for (int i = 0; i < getLogRecSize(); i++) {
+ pw.println(" rec[" + i + "]: " + getLogRec(i).toString());
+ pw.flush();
+ }
+ pw.println("curState=" + getCurrentState().getName());
+ }
+
+ @Override
+ public String toString() {
+ String name = "(null)";
+ String state = "(null)";
+ try {
+ name = mName.toString();
+ state = mSmHandler.getCurrentState().getName().toString();
+ } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
+ // Will use default(s) initialized above.
+ }
+ return "name=" + name + " state=" + state;
+ }
+
+ /**
+ * Log with debug and add to the LogRecords.
+ *
+ * @param s is string log
+ */
+ protected void logAndAddLogRec(String s) {
+ addLogRec(s);
+ log(s);
+ }
+
+ /**
+ * Log with debug
+ *
+ * @param s is string log
+ */
+ protected void log(String s) {
+ Log.d(mName, s);
+ }
+
+ /**
+ * Log with debug attribute
+ *
+ * @param s is string log
+ */
+ protected void logd(String s) {
+ Log.d(mName, s);
+ }
+
+ /**
+ * Log with verbose attribute
+ *
+ * @param s is string log
+ */
+ protected void logv(String s) {
+ Log.v(mName, s);
+ }
+
+ /**
+ * Log with info attribute
+ *
+ * @param s is string log
+ */
+ protected void logi(String s) {
+ Log.i(mName, s);
+ }
+
+ /**
+ * Log with warning attribute
+ *
+ * @param s is string log
+ */
+ protected void logw(String s) {
+ Log.w(mName, s);
+ }
+
+ /**
+ * Log with error attribute
+ *
+ * @param s is string log
+ */
+ protected void loge(String s) {
+ Log.e(mName, s);
+ }
+
+ /**
+ * Log with error attribute
+ *
+ * @param s is string log
+ * @param e is a Throwable which logs additional information.
+ */
+ protected void loge(String s, Throwable e) {
+ Log.e(mName, s, e);
+ }
+}
diff --git a/src/com/android/bluetooth/util/DevicePolicyUtils.java b/src/com/android/bluetooth/util/DevicePolicyUtils.java
index 5f11a29..0af4cf7 100644
--- a/src/com/android/bluetooth/util/DevicePolicyUtils.java
+++ b/src/com/android/bluetooth/util/DevicePolicyUtils.java
@@ -37,7 +37,7 @@
// Check each user.
for (UserInfo ui : userInfoList) {
- if (!ui.isManagedProfile()) {
+ if (!userManager.isManagedProfile(ui.id)) {
continue; // Not a managed user.
}
return dpm.getBluetoothContactSharingDisabled(new UserHandle(ui.id));
diff --git a/src/com/android/bluetooth/util/GsmAlphabet.java b/src/com/android/bluetooth/util/GsmAlphabet.java
new file mode 100644
index 0000000..e723208
--- /dev/null
+++ b/src/com/android/bluetooth/util/GsmAlphabet.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2019 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.bluetooth.util;
+
+import android.content.res.Resources;
+import android.telephony.Rlog;
+import android.util.SparseIntArray;
+
+import com.android.internal.R;
+
+/**
+ * This class implements the character set mapping between
+ * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1
+ * and UTF-16
+ *
+ * {@hide}
+ */
+public class GsmAlphabet {
+ private static final String TAG = "GSM";
+
+ /**
+ * This escapes extended characters, and when present indicates that the
+ * following character should be looked up in the "extended" table.
+ *
+ * gsmToChar(GSM_EXTENDED_ESCAPE) returns 0xffff
+ */
+ public static final byte GSM_EXTENDED_ESCAPE = 0x1B;
+
+ /**
+ * User data header requires one octet for length. Count as one septet, because
+ * all combinations of header elements below will have at least one free bit
+ * when padding to the nearest septet boundary.
+ */
+ public static final int UDH_SEPTET_COST_LENGTH = 1;
+
+ /**
+ * Using a non-default language locking shift table OR single shift table
+ * requires a user data header of 3 octets, or 4 septets, plus UDH length.
+ */
+ public static final int UDH_SEPTET_COST_ONE_SHIFT_TABLE = 4;
+
+ /**
+ * Using a non-default language locking shift table AND single shift table
+ * requires a user data header of 6 octets, or 7 septets, plus UDH length.
+ */
+ public static final int UDH_SEPTET_COST_TWO_SHIFT_TABLES = 7;
+
+ /**
+ * Multi-part messages require a user data header of 5 octets, or 6 septets,
+ * plus UDH length.
+ */
+ public static final int UDH_SEPTET_COST_CONCATENATED_MESSAGE = 6;
+
+ /**
+ * For a specific text string, this object describes protocol
+ * properties of encoding it for transmission as message user
+ * data.
+ */
+ public static class TextEncodingDetails {
+
+ public TextEncodingDetails() {
+ }
+
+ /**
+ * The number of SMS's required to encode the text.
+ */
+ public int msgCount;
+
+ /**
+ * The number of code units consumed so far, where code units
+ * are basically characters in the encoding -- for example,
+ * septets for the standard ASCII and GSM encodings, and 16
+ * bits for Unicode.
+ */
+ public int codeUnitCount;
+
+ /**
+ * How many code units are still available without spilling
+ * into an additional message.
+ */
+ public int codeUnitsRemaining;
+
+ /**
+ * The encoding code unit size (specified using
+ * android.telephony.SmsMessage ENCODING_*).
+ */
+ public int codeUnitSize;
+
+ /**
+ * The GSM national language table to use, or 0 for the default 7-bit alphabet.
+ */
+ public int languageTable;
+
+ /**
+ * The GSM national language shift table to use, or 0 for the default 7-bit extension table.
+ */
+ public int languageShiftTable;
+
+ @Override
+ public String toString() {
+ return "TextEncodingDetails "
+ + "{ msgCount=" + msgCount
+ + ", codeUnitCount=" + codeUnitCount
+ + ", codeUnitsRemaining=" + codeUnitsRemaining
+ + ", codeUnitSize=" + codeUnitSize
+ + ", languageTable=" + languageTable
+ + ", languageShiftTable=" + languageShiftTable
+ + " }";
+ }
+ }
+
+ /**
+ * Convert a GSM alphabet 7 bit packed string (SMS string) into a
+ * {@link java.lang.String}.
+ *
+ * See TS 23.038 6.1.2.1 for SMS Character Packing
+ *
+ * @param pdu the raw data from the pdu
+ * @param offset the byte offset of
+ * @param lengthSeptets string length in septets, not bytes
+ * @param numPaddingBits the number of padding bits before the start of the
+ * string in the first byte
+ * @param languageTable the 7 bit language table, or 0 for the default GSM alphabet
+ * @param shiftTable the 7 bit single shift language table, or 0 for the default
+ * GSM extension table
+ * @return String representation or null on decoding exception
+ */
+ public static String gsm7BitPackedToString(byte[] pdu, int offset, int lengthSeptets,
+ int numPaddingBits, int languageTable,
+ int shiftTable) {
+ StringBuilder ret = new StringBuilder(lengthSeptets);
+
+ if (languageTable < 0 || languageTable > sLanguageTables.length) {
+ Rlog.w(TAG, "unknown language table " + languageTable + ", using default");
+ languageTable = 0;
+ }
+ if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) {
+ Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default");
+ shiftTable = 0;
+ }
+
+ try {
+ boolean prevCharWasEscape = false;
+ String languageTableToChar = sLanguageTables[languageTable];
+ String shiftTableToChar = sLanguageShiftTables[shiftTable];
+
+ if (languageTableToChar.isEmpty()) {
+ Rlog.w(TAG, "no language table for code " + languageTable + ", using default");
+ languageTableToChar = sLanguageTables[0];
+ }
+ if (shiftTableToChar.isEmpty()) {
+ Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default");
+ shiftTableToChar = sLanguageShiftTables[0];
+ }
+
+ for (int i = 0; i < lengthSeptets; i++) {
+ int bitOffset = (7 * i) + numPaddingBits;
+
+ int byteOffset = bitOffset / 8;
+ int shift = bitOffset % 8;
+ int gsmVal;
+
+ gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
+
+ // if it crosses a byte boundary
+ if (shift > 1) {
+ // set msb bits to 0
+ gsmVal &= 0x7f >> (shift - 1);
+
+ gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
+ }
+
+ if (prevCharWasEscape) {
+ if (gsmVal == GSM_EXTENDED_ESCAPE) {
+ ret.append(' '); // display ' ' for reserved double escape sequence
+ } else {
+ char c = shiftTableToChar.charAt(gsmVal);
+ if (c == ' ') {
+ ret.append(languageTableToChar.charAt(gsmVal));
+ } else {
+ ret.append(c);
+ }
+ }
+ prevCharWasEscape = false;
+ } else if (gsmVal == GSM_EXTENDED_ESCAPE) {
+ prevCharWasEscape = true;
+ } else {
+ ret.append(languageTableToChar.charAt(gsmVal));
+ }
+ }
+ } catch (RuntimeException ex) {
+ Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
+ return null;
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Convert a string into an 8-bit unpacked GSM alphabet byte array.
+ * Always uses GSM default 7-bit alphabet and extension table.
+ * @param s the string to encode
+ * @return the 8-bit GSM encoded byte array for the string
+ */
+ public static byte[] stringToGsm8BitPacked(String s) {
+ byte[] ret;
+
+ int septets = countGsmSeptetsUsingTables(s, true, 0, 0);
+
+ // Enough for all the septets and the length byte prefix
+ ret = new byte[septets];
+
+ stringToGsm8BitUnpackedField(s, ret, 0, ret.length);
+
+ return ret;
+ }
+
+ /**
+ * Write a String into a GSM 8-bit unpacked field of
+ * Field is padded with 0xff's, string is truncated if necessary
+ *
+ * @param s the string to encode
+ * @param dest the destination byte array
+ * @param offset the starting offset for the encoded string
+ * @param length the maximum number of bytes to write
+ */
+ public static void stringToGsm8BitUnpackedField(String s, byte[] dest, int offset,
+ int length) {
+ int outByteIndex = offset;
+ SparseIntArray charToLanguageTable = sCharsToGsmTables[0];
+ SparseIntArray charToShiftTable = sCharsToShiftTables[0];
+
+ // Septets are stored in byte-aligned octets
+ for (int i = 0, sz = s.length(); i < sz && (outByteIndex - offset) < length; i++) {
+ char c = s.charAt(i);
+ int v = charToLanguageTable.get(c, -1);
+ if (v == -1) {
+ v = charToShiftTable.get(c, -1);
+ if (v == -1) {
+ v = charToLanguageTable.get(' ', ' '); // fall back to ASCII space
+ } else {
+ // make sure we can fit an escaped char
+ if (!(outByteIndex + 1 - offset < length)) {
+ break;
+ }
+
+ dest[outByteIndex++] = GSM_EXTENDED_ESCAPE;
+ }
+ }
+
+ dest[outByteIndex++] = (byte) v;
+ }
+
+ // pad with 0xff's
+ while ((outByteIndex - offset) < length) {
+ dest[outByteIndex++] = (byte) 0xff;
+ }
+ }
+
+ /**
+ * Returns the count of 7-bit GSM alphabet characters needed
+ * to represent this string, using the specified 7-bit language table
+ * and extension table (0 for GSM default tables).
+ * @param s the Unicode string that will be encoded
+ * @param use7bitOnly allow using space in place of unencodable character if true,
+ * otherwise, return -1 if any characters are unencodable
+ * @param languageTable the 7 bit language table, or 0 for the default GSM alphabet
+ * @param languageShiftTable the 7 bit single shift language table, or 0 for the default
+ * GSM extension table
+ * @return the septet count for s using the specified language tables, or -1 if any
+ * characters are unencodable and use7bitOnly is false
+ */
+ public static int countGsmSeptetsUsingTables(CharSequence s, boolean use7bitOnly,
+ int languageTable, int languageShiftTable) {
+ int count = 0;
+ int sz = s.length();
+ SparseIntArray charToLanguageTable = sCharsToGsmTables[languageTable];
+ SparseIntArray charToShiftTable = sCharsToShiftTables[languageShiftTable];
+ for (int i = 0; i < sz; i++) {
+ char c = s.charAt(i);
+ if (c == GSM_EXTENDED_ESCAPE) {
+ Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
+ continue;
+ }
+ if (charToLanguageTable.get(c, -1) != -1) {
+ count++;
+ } else if (charToShiftTable.get(c, -1) != -1) {
+ count += 2; // escape + shift table index
+ } else if (use7bitOnly) {
+ count++; // encode as space
+ } else {
+ return -1; // caller must check for this case
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Enable country-specific language tables from MCC-specific overlays.
+ * @context the context to use to get the TelephonyManager
+ */
+ private static void enableCountrySpecificEncodings() {
+ Resources r = Resources.getSystem();
+ // See comments in frameworks/base/core/res/res/values/config.xml for allowed values
+ sEnabledSingleShiftTables = r.getIntArray(R.array.config_sms_enabled_single_shift_tables);
+ sEnabledLockingShiftTables = r.getIntArray(R.array.config_sms_enabled_locking_shift_tables);
+
+ if (sEnabledSingleShiftTables.length > 0) {
+ sHighestEnabledSingleShiftCode =
+ sEnabledSingleShiftTables[sEnabledSingleShiftTables.length - 1];
+ } else {
+ sHighestEnabledSingleShiftCode = 0;
+ }
+ }
+
+ /** Reverse mapping from Unicode characters to indexes into language tables. */
+ private static final SparseIntArray[] sCharsToGsmTables;
+
+ /** Reverse mapping from Unicode characters to indexes into language shift tables. */
+ private static final SparseIntArray[] sCharsToShiftTables;
+
+ /** OEM configured list of enabled national language single shift tables for encoding. */
+ private static int[] sEnabledSingleShiftTables;
+
+ /** OEM configured list of enabled national language locking shift tables for encoding. */
+ private static int[] sEnabledLockingShiftTables;
+
+ /** Highest language code to include in array of single shift counters. */
+ private static int sHighestEnabledSingleShiftCode;
+
+ /**
+ * GSM default 7 bit alphabet plus national language locking shift character tables.
+ * Comment lines above strings indicate the lower four bits of the table position.
+ */
+ private static final String[] sLanguageTables = {
+ /* 3GPP TS 23.038 V9.1.1 section 6.2.1 - GSM 7 bit Default Alphabet
+ 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....0.....1 */
+ "@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5\u0394_"
+ // 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+ + "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u00c6\u00e6\u00df"
+ // F.....012.34.....56789ABCDEF0123456789ABCDEF0.....123456789ABCDEF0123456789A
+ + "\u00c9 !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ // B.....C.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+ + "\u00c4\u00d6\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1"
+ // E.....F.....
+ + "\u00fc\u00e0",
+
+ /* A.3.1 Turkish National Language Locking Shift Table
+ 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....0.....1 */
+ "@\u00a3$\u00a5\u20ac\u00e9\u00f9\u0131\u00f2\u00c7\n\u011e\u011f\r\u00c5\u00e5\u0394_"
+ // 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+ + "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u015e\u015f\u00df"
+ // F.....012.34.....56789ABCDEF0123456789ABCDEF0.....123456789ABCDEF0123456789A
+ + "\u00c9 !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u0130ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ // B.....C.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+ + "\u00c4\u00d6\u00d1\u00dc\u00a7\u00e7abcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1"
+ // E.....F.....
+ + "\u00fc\u00e0",
+
+ /* A.3.2 Void (no locking shift table for Spanish) */
+ "",
+
+ /* A.3.3 Portuguese National Language Locking Shift Table
+ 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....0.....1 */
+ "@\u00a3$\u00a5\u00ea\u00e9\u00fa\u00ed\u00f3\u00e7\n\u00d4\u00f4\r\u00c1\u00e1\u0394_"
+ // 2.....3.....4.....5.....67.8.....9.....AB.....C.....D.....E.....F.....012.34.....
+ + "\u00aa\u00c7\u00c0\u221e^\\\u20ac\u00d3|\uffff\u00c2\u00e2\u00ca\u00c9 !\"#\u00ba"
+ // 56789ABCDEF0123456789ABCDEF0.....123456789ABCDEF0123456789AB.....C.....D.....E.....
+ + "%&'()*+,-./0123456789:;<=>?\u00cdABCDEFGHIJKLMNOPQRSTUVWXYZ\u00c3\u00d5\u00da\u00dc"
+ // F.....0123456789ABCDEF0123456789AB.....C.....DE.....F.....
+ + "\u00a7~abcdefghijklmnopqrstuvwxyz\u00e3\u00f5`\u00fc\u00e0",
+
+ /* A.3.4 Bengali National Language Locking Shift Table
+ 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.EF.....0..... */
+ "\u0981\u0982\u0983\u0985\u0986\u0987\u0988\u0989\u098a\u098b\n\u098c \r \u098f\u0990"
+ // 123.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + " \u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\uffff\u099b\u099c\u099d\u099e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABC
+ + " !\u099f\u09a0\u09a1\u09a2\u09a3\u09a4)(\u09a5\u09a6,\u09a7.\u09a80123456789:; "
+ // D.....E.....F0.....1.....2.....3.....4.....56.....789A.....B.....C.....D.....
+ + "\u09aa\u09ab?\u09ac\u09ad\u09ae\u09af\u09b0 \u09b2 \u09b6\u09b7\u09b8\u09b9"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....789.....A.....BCD.....E.....
+ + "\u09bc\u09bd\u09be\u09bf\u09c0\u09c1\u09c2\u09c3\u09c4 \u09c7\u09c8 \u09cb\u09cc"
+ // F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....F.....
+ + "\u09cd\u09ceabcdefghijklmnopqrstuvwxyz\u09d7\u09dc\u09dd\u09f0\u09f1",
+
+ /* A.3.5 Gujarati National Language Locking Shift Table
+ 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.EF.....0.....*/
+ "\u0a81\u0a82\u0a83\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\n\u0a8c\u0a8d\r \u0a8f\u0a90"
+ // 1.....23.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+ + "\u0a91 \u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\uffff\u0a9b\u0a9c\u0a9d"
+ // F.....012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789AB
+ + "\u0a9e !\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4)(\u0aa5\u0aa6,\u0aa7.\u0aa80123456789:;"
+ // CD.....E.....F0.....1.....2.....3.....4.....56.....7.....89.....A.....B.....C.....
+ + " \u0aaa\u0aab?\u0aac\u0aad\u0aae\u0aaf\u0ab0 \u0ab2\u0ab3 \u0ab5\u0ab6\u0ab7\u0ab8"
+ // D.....E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....
+ + "\u0ab9\u0abc\u0abd\u0abe\u0abf\u0ac0\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5 \u0ac7\u0ac8"
+ // B.....CD.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....
+ + "\u0ac9 \u0acb\u0acc\u0acd\u0ad0abcdefghijklmnopqrstuvwxyz\u0ae0\u0ae1\u0ae2\u0ae3"
+ // F.....
+ + "\u0af1",
+
+ /* A.3.6 Hindi National Language Locking Shift Table
+ 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....*/
+ "\u0901\u0902\u0903\u0905\u0906\u0907\u0908\u0909\u090a\u090b\n\u090c\u090d\r\u090e\u090f"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....
+ + "\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\uffff\u091b\u091c"
+ // E.....F.....012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....012345
+ + "\u091d\u091e !\u091f\u0920\u0921\u0922\u0923\u0924)(\u0925\u0926,\u0927.\u0928012345"
+ // 6789ABC.....D.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....8.....
+ + "6789:;\u0929\u092a\u092b?\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934"
+ // 9.....A.....B.....C.....D.....E.....F.....0.....1.....2.....3.....4.....5.....6.....
+ + "\u0935\u0936\u0937\u0938\u0939\u093c\u093d\u093e\u093f\u0940\u0941\u0942\u0943\u0944"
+ // 7.....8.....9.....A.....B.....C.....D.....E.....F.....0.....123456789ABCDEF012345678
+ + "\u0945\u0946\u0947\u0948\u0949\u094a\u094b\u094c\u094d\u0950abcdefghijklmnopqrstuvwx"
+ // 9AB.....C.....D.....E.....F.....
+ + "yz\u0972\u097b\u097c\u097e\u097f",
+
+ /* A.3.7 Kannada National Language Locking Shift Table
+ NOTE: TS 23.038 V9.1.1 shows code 0x24 as \u0caa, corrected to \u0ca1 (typo)
+ 01.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....0.....1 */
+ " \u0c82\u0c83\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\n\u0c8c \r\u0c8e\u0c8f\u0c90 "
+ // 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\uffff\u0c9b\u0c9c\u0c9d\u0c9e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABC
+ + " !\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4)(\u0ca5\u0ca6,\u0ca7.\u0ca80123456789:; "
+ // D.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....
+ + "\u0caa\u0cab?\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3 \u0cb5\u0cb6\u0cb7"
+ // C.....D.....E.....F.....0.....1.....2.....3.....4.....5.....6.....78.....9.....
+ + "\u0cb8\u0cb9\u0cbc\u0cbd\u0cbe\u0cbf\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4 \u0cc6\u0cc7"
+ // A.....BC.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+ + "\u0cc8 \u0cca\u0ccb\u0ccc\u0ccd\u0cd5abcdefghijklmnopqrstuvwxyz\u0cd6\u0ce0\u0ce1"
+ // E.....F.....
+ + "\u0ce2\u0ce3",
+
+ /* A.3.8 Malayalam National Language Locking Shift Table
+ 01.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....0.....1 */
+ " \u0d02\u0d03\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\n\u0d0c \r\u0d0e\u0d0f\u0d10 "
+ // 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....
+ + "\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\uffff\u0d1b\u0d1c\u0d1d\u0d1e"
+ // 012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABC
+ + " !\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24)(\u0d25\u0d26,\u0d27.\u0d280123456789:; "
+ // D.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....
+ + "\u0d2a\u0d2b?\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36"
+ // B.....C.....D.....EF.....0.....1.....2.....3.....4.....5.....6.....78.....9.....
+ + "\u0d37\u0d38\u0d39 \u0d3d\u0d3e\u0d3f\u0d40\u0d41\u0d42\u0d43\u0d44 \u0d46\u0d47"
+ // A.....BC.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+ + "\u0d48 \u0d4a\u0d4b\u0d4c\u0d4d\u0d57abcdefghijklmnopqrstuvwxyz\u0d60\u0d61\u0d62"
+ // E.....F.....
+ + "\u0d63\u0d79",
+
+ /* A.3.9 Oriya National Language Locking Shift Table
+ 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.EF.....0.....12 */
+ "\u0b01\u0b02\u0b03\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\n\u0b0c \r \u0b0f\u0b10 "
+ // 3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....01
+ + "\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\uffff\u0b1b\u0b1c\u0b1d\u0b1e !"
+ // 2.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABCD.....
+ + "\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24)(\u0b25\u0b26,\u0b27.\u0b280123456789:; \u0b2a"
+ // E.....F0.....1.....2.....3.....4.....56.....7.....89.....A.....B.....C.....D.....
+ + "\u0b2b?\u0b2c\u0b2d\u0b2e\u0b2f\u0b30 \u0b32\u0b33 \u0b35\u0b36\u0b37\u0b38\u0b39"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....789.....A.....BCD.....E.....
+ + "\u0b3c\u0b3d\u0b3e\u0b3f\u0b40\u0b41\u0b42\u0b43\u0b44 \u0b47\u0b48 \u0b4b\u0b4c"
+ // F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....F.....
+ + "\u0b4d\u0b56abcdefghijklmnopqrstuvwxyz\u0b57\u0b60\u0b61\u0b62\u0b63",
+
+ /* A.3.10 Punjabi National Language Locking Shift Table
+ 0.....1.....2.....3.....4.....5.....6.....7.....8.....9A.BCD.EF.....0.....123.....4.....*/
+ "\u0a01\u0a02\u0a03\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a \n \r \u0a0f\u0a10 \u0a13\u0a14"
+ // 5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....F.....012.....3.....
+ + "\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\uffff\u0a1b\u0a1c\u0a1d\u0a1e !\u0a1f\u0a20"
+ // 4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789ABCD.....E.....F0.....
+ + "\u0a21\u0a22\u0a23\u0a24)(\u0a25\u0a26,\u0a27.\u0a280123456789:; \u0a2a\u0a2b?\u0a2c"
+ // 1.....2.....3.....4.....56.....7.....89.....A.....BC.....D.....E.....F0.....1.....
+ + "\u0a2d\u0a2e\u0a2f\u0a30 \u0a32\u0a33 \u0a35\u0a36 \u0a38\u0a39\u0a3c \u0a3e\u0a3f"
+ // 2.....3.....4.....56789.....A.....BCD.....E.....F.....0.....123456789ABCDEF012345678
+ + "\u0a40\u0a41\u0a42 \u0a47\u0a48 \u0a4b\u0a4c\u0a4d\u0a51abcdefghijklmnopqrstuvwx"
+ // 9AB.....C.....D.....E.....F.....
+ + "yz\u0a70\u0a71\u0a72\u0a73\u0a74",
+
+ /* A.3.11 Tamil National Language Locking Shift Table
+ 01.....2.....3.....4.....5.....6.....7.....8.....9A.BCD.E.....F.....0.....12.....3..... */
+ " \u0b82\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a \n \r\u0b8e\u0b8f\u0b90 \u0b92\u0b93"
+ // 4.....5.....6789.....A.....B.....CD.....EF.....012.....3456.....7.....89ABCDEF.....
+ + "\u0b94\u0b95 \u0b99\u0b9a\uffff \u0b9c \u0b9e !\u0b9f \u0ba3\u0ba4)( , .\u0ba8"
+ // 0123456789ABC.....D.....EF012.....3.....4.....5.....6.....7.....8.....9.....A.....
+ + "0123456789:;\u0ba9\u0baa ? \u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6"
+ // B.....C.....D.....EF0.....1.....2.....3.....4.....5678.....9.....A.....BC.....D.....
+ + "\u0bb7\u0bb8\u0bb9 \u0bbe\u0bbf\u0bc0\u0bc1\u0bc2 \u0bc6\u0bc7\u0bc8 \u0bca\u0bcb"
+ // E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....F.....
+ + "\u0bcc\u0bcd\u0bd0abcdefghijklmnopqrstuvwxyz\u0bd7\u0bf0\u0bf1\u0bf2\u0bf9",
+
+ /* A.3.12 Telugu National Language Locking Shift Table
+ 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....CD.E.....F.....0.....*/
+ "\u0c01\u0c02\u0c03\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\n\u0c0c \r\u0c0e\u0c0f\u0c10"
+ // 12.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+ + " \u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\uffff\u0c1b\u0c1c\u0c1d"
+ // F.....012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....0123456789AB
+ + "\u0c1e !\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24)(\u0c25\u0c26,\u0c27.\u0c280123456789:;"
+ // CD.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....89.....A.....B.....
+ + " \u0c2a\u0c2b?\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33 \u0c35\u0c36\u0c37"
+ // C.....D.....EF.....0.....1.....2.....3.....4.....5.....6.....78.....9.....A.....B
+ + "\u0c38\u0c39 \u0c3d\u0c3e\u0c3f\u0c40\u0c41\u0c42\u0c43\u0c44 \u0c46\u0c47\u0c48 "
+ // C.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....E.....
+ + "\u0c4a\u0c4b\u0c4c\u0c4d\u0c55abcdefghijklmnopqrstuvwxyz\u0c56\u0c60\u0c61\u0c62"
+ // F.....
+ + "\u0c63",
+
+ /* A.3.13 Urdu National Language Locking Shift Table
+ 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....*/
+ "\u0627\u0622\u0628\u067b\u0680\u067e\u06a6\u062a\u06c2\u067f\n\u0679\u067d\r\u067a\u067c"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....
+ + "\u062b\u062c\u0681\u0684\u0683\u0685\u0686\u0687\u062d\u062e\u062f\uffff\u068c\u0688"
+ // E.....F.....012.....3.....4.....5.....6.....7.....89A.....B.....CD.....EF.....012345
+ + "\u0689\u068a !\u068f\u068d\u0630\u0631\u0691\u0693)(\u0699\u0632,\u0696.\u0698012345"
+ // 6789ABC.....D.....E.....F0.....1.....2.....3.....4.....5.....6.....7.....8.....
+ + "6789:;\u069a\u0633\u0634?\u0635\u0636\u0637\u0638\u0639\u0641\u0642\u06a9\u06aa"
+ // 9.....A.....B.....C.....D.....E.....F.....0.....1.....2.....3.....4.....5.....6.....
+ + "\u06ab\u06af\u06b3\u06b1\u0644\u0645\u0646\u06ba\u06bb\u06bc\u0648\u06c4\u06d5\u06c1"
+ // 7.....8.....9.....A.....B.....C.....D.....E.....F.....0.....123456789ABCDEF012345678
+ + "\u06be\u0621\u06cc\u06d0\u06d2\u064d\u0650\u064f\u0657\u0654abcdefghijklmnopqrstuvwx"
+ // 9AB.....C.....D.....E.....F.....
+ + "yz\u0655\u0651\u0653\u0656\u0670"
+ };
+
+ /**
+ * GSM default extension table plus national language single shift character tables.
+ */
+ private static final String[] sLanguageShiftTables = new String[]{
+ /* 6.2.1.1 GSM 7 bit Default Alphabet Extension Table
+ 0123456789A.....BCDEF0123456789ABCDEF0123456789ABCDEF.0123456789ABCDEF0123456789ABCDEF */
+ " \u000c ^ {} \\ [~] | "
+ // 0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+ + " \u20ac ",
+
+ /* A.2.1 Turkish National Language Single Shift Table
+ 0123456789A.....BCDEF0123456789ABCDEF0123456789ABCDEF.0123456789ABCDEF01234567.....8 */
+ " \u000c ^ {} \\ [~] | \u011e "
+ // 9.....ABCDEF0123.....456789ABCDEF0123.....45.....67.....89.....ABCDEF0123.....
+ + "\u0130 \u015e \u00e7 \u20ac \u011f \u0131 \u015f"
+ // 456789ABCDEF
+ + " ",
+
+ /* A.2.2 Spanish National Language Single Shift Table
+ 0123456789.....A.....BCDEF0123456789ABCDEF0123456789ABCDEF.0123456789ABCDEF01.....23 */
+ " \u00e7\u000c ^ {} \\ [~] |\u00c1 "
+ // 456789.....ABCDEF.....012345.....6789ABCDEF01.....2345.....6789.....ABCDEF.....012
+ + " \u00cd \u00d3 \u00da \u00e1 \u20ac \u00ed \u00f3 "
+ // 345.....6789ABCDEF
+ + " \u00fa ",
+
+ /* A.2.3 Portuguese National Language Single Shift Table
+ 012345.....6789.....A.....B.....C.....DE.....F.....012.....3.....45.....6.....7.....8....*/
+ " \u00ea \u00e7\u000c\u00d4\u00f4 \u00c1\u00e1 \u03a6\u0393^\u03a9\u03a0\u03a8\u03a3"
+ // 9.....ABCDEF.....0123456789ABCDEF.0123456789ABCDEF01.....23456789.....ABCDE
+ + "\u0398 \u00ca {} \\ [~] |\u00c0 \u00cd "
+ // F.....012345.....6789AB.....C.....DEF01.....2345.....6789.....ABCDEF.....01234
+ + "\u00d3 \u00da \u00c3\u00d5 \u00c2 \u20ac \u00ed \u00f3 "
+ // 5.....6789AB.....C.....DEF.....
+ + "\u00fa \u00e3\u00f5 \u00e2",
+
+ /* A.2.4 Bengali National Language Single Shift Table
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u09e6\u09e7 \u09e8\u09e9"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+ + "\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u09df\u09e0\u09e1\u09e2{}\u09e3\u09f2\u09f3"
+ // D.....E.....F.0.....1.....2.....3.....4.....56789ABCDEF0123456789ABCDEF
+ + "\u09f4\u09f5\\\u09f6\u09f7\u09f8\u09f9\u09fa [~] |ABCDEFGHIJKLMNO"
+ // 0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+ + "PQRSTUVWXYZ \u20ac ",
+
+ /* A.2.5 Gujarati National Language Single Shift Table
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0ae6\u0ae7"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6789ABCDEF.0123456789ABCDEF
+ + "\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef {} \\ [~] "
+ // 0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+ + "|ABCDEFGHIJKLMNOPQRSTUVWXYZ \u20ac ",
+
+ /* A.2.6 Hindi National Language Single Shift Table
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0966\u0967"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+ + "\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u0951\u0952{}\u0953\u0954\u0958"
+ // D.....E.....F.0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....
+ + "\u0959\u095a\\\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u0962\u0963\u0970\u0971"
+ // BCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+ + " [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ \u20ac ",
+
+ /* A.2.7 Kannada National Language Single Shift Table
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0ce6\u0ce7"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....BCDEF.01234567
+ + "\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0cde\u0cf1{}\u0cf2 \\ "
+ // 89ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+ + " [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ \u20ac ",
+
+ /* A.2.8 Malayalam National Language Single Shift Table
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0d66\u0d67"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+ + "\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0d70\u0d71{}\u0d72\u0d73\u0d74"
+ // D.....E.....F.0.....1.....2.....3.....4.....56789ABCDEF0123456789ABCDEF0123456789A
+ + "\u0d75\u0d7a\\\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ // BCDEF012345.....6789ABCDEF0123456789ABCDEF
+ + " \u20ac ",
+
+ /* A.2.9 Oriya National Language Single Shift Table
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0b66\u0b67"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....DE
+ + "\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0b5c\u0b5d{}\u0b5f\u0b70\u0b71 "
+ // F.0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789A
+ + "\\ [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ \u20ac "
+ // BCDEF
+ + " ",
+
+ /* A.2.10 Punjabi National Language Single Shift Table
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0a66\u0a67"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+ + "\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0a59\u0a5a{}\u0a5b\u0a5c\u0a5e"
+ // D.....EF.0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF01
+ + "\u0a75 \\ [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ \u20ac "
+ // 23456789ABCDEF
+ + " ",
+
+ /* A.2.11 Tamil National Language Single Shift Table
+ NOTE: TS 23.038 V9.1.1 shows code 0x24 as \u0bef, corrected to \u0bee (typo)
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0964\u0965 \u0be6\u0be7"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+ + "\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0bf3\u0bf4{}\u0bf5\u0bf6\u0bf7"
+ // D.....E.....F.0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABC
+ + "\u0bf8\u0bfa\\ [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ \u20ac "
+ // DEF0123456789ABCDEF
+ + " ",
+
+ /* A.2.12 Telugu National Language Single Shift Table
+ NOTE: TS 23.038 V9.1.1 shows code 0x22-0x23 as \u06cc\u06cd, corrected to \u0c6c\u0c6d
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789ABC.....D.....E.....F..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#* \u0c66\u0c67\u0c68\u0c69"
+ // 0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....D.....E.....F.
+ + "\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0c58\u0c59{}\u0c78\u0c79\u0c7a\u0c7b\u0c7c\\"
+ // 0.....1.....2.....3456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345.....6789ABCD
+ + "\u0c7d\u0c7e\u0c7f [~] |ABCDEFGHIJKLMNOPQRSTUVWXYZ \u20ac "
+ // EF0123456789ABCDEF
+ + " ",
+
+ /* A.2.13 Urdu National Language Single Shift Table
+ 01.....23.....4.....5.6.....789A.....BCDEF0123.....45.....6789.....A.....BC.....D..... */
+ "@\u00a3$\u00a5\u00bf\"\u00a4%&'\u000c*+ -/<=>\u00a1^\u00a1_#*\u0600\u0601 \u06f0\u06f1"
+ // E.....F.....0.....1.....2.....3.....4.....5.....6.....7.....89A.....B.....C.....
+ + "\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u060c\u060d{}\u060e\u060f\u0610"
+ // D.....E.....F.0.....1.....2.....3.....4.....5.....6.....7.....8.....9.....A.....
+ + "\u0611\u0612\\\u0613\u0614\u061b\u061f\u0640\u0652\u0658\u066b\u066c\u0672\u0673"
+ // B.....CDEF.....0123456789ABCDEF0123456789ABCDEF012345.....6789ABCDEF0123456789ABCDEF
+ + "\u06cd[~]\u06d4|ABCDEFGHIJKLMNOPQRSTUVWXYZ \u20ac "
+ };
+
+ static {
+ enableCountrySpecificEncodings();
+ int numTables = sLanguageTables.length;
+ int numShiftTables = sLanguageShiftTables.length;
+ if (numTables != numShiftTables) {
+ Rlog.e(TAG, "Error: language tables array length " + numTables
+ + " != shift tables array length " + numShiftTables);
+ }
+
+ sCharsToGsmTables = new SparseIntArray[numTables];
+ for (int i = 0; i < numTables; i++) {
+ String table = sLanguageTables[i];
+
+ int tableLen = table.length();
+ if (tableLen != 0 && tableLen != 128) {
+ Rlog.e(TAG, "Error: language tables index " + i
+ + " length " + tableLen + " (expected 128 or 0)");
+ }
+
+ SparseIntArray charToGsmTable = new SparseIntArray(tableLen);
+ sCharsToGsmTables[i] = charToGsmTable;
+ for (int j = 0; j < tableLen; j++) {
+ char c = table.charAt(j);
+ charToGsmTable.put(c, j);
+ }
+ }
+
+ sCharsToShiftTables = new SparseIntArray[numShiftTables];
+ for (int i = 0; i < numShiftTables; i++) {
+ String shiftTable = sLanguageShiftTables[i];
+
+ int shiftTableLen = shiftTable.length();
+ if (shiftTableLen != 0 && shiftTableLen != 128) {
+ Rlog.e(TAG, "Error: language shift tables index " + i
+ + " length " + shiftTableLen + " (expected 128 or 0)");
+ }
+
+ SparseIntArray charToShiftTable = new SparseIntArray(shiftTableLen);
+ sCharsToShiftTables[i] = charToShiftTable;
+ for (int j = 0; j < shiftTableLen; j++) {
+ char c = shiftTable.charAt(j);
+ if (c != ' ') {
+ charToShiftTable.put(c, j);
+ }
+ }
+ }
+ }
+}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
new file mode 100644
index 0000000..68b70c2
--- /dev/null
+++ b/tests/unit/Android.bp
@@ -0,0 +1,37 @@
+android_test {
+ name: "BluetoothInstrumentationTests",
+
+ // We only want this apk build for tests.
+ certificate: "platform",
+
+ libs: [
+ "javax.obex",
+ "android.test.runner",
+ "telephony-common",
+ "libprotobuf-java-micro",
+ "android.test.base",
+ "android.test.mock",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "mockito-target",
+ "androidx.test.espresso.intents",
+ "gson-prebuilt-jar",
+ "androidx.room_room-migration",
+ "androidx.room_room-runtime",
+ "androidx.room_room-testing",
+ ],
+
+ asset_dirs: ["src/com/android/bluetooth/btservice/storage/schemas"],
+
+ // Include all test java files.
+ srcs: ["src/**/*.java"],
+
+ platform_apis: true,
+
+ test_suites: ["device-tests"],
+
+ instrumentation_for: "Bluetooth",
+
+}
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
deleted file mode 100644
index 9bfd4ab..0000000
--- a/tests/unit/Android.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_JAVA_LIBRARIES := \
- javax.obex \
- android.test.runner \
- telephony-common \
- libprotobuf-java-micro \
- android.test.base \
- android.test.mock
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx.test.rules \
- mockito-target \
- androidx.test.espresso.intents \
- gson-prebuilt-jar \
- bt-androidx-room-migration-nodeps \
- bt-androidx-room-runtime-nodeps \
- bt-androidx-room-testing-nodeps
-
-LOCAL_ASSET_DIR += \
- $(LOCAL_PATH)/src/com/android/bluetooth/btservice/storage/schemas
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := BluetoothInstrumentationTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_INSTRUMENTATION_FOR := Bluetooth
-
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-
-ROOM_LIBS_PATH := ../../lib/room
-
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
- bt-androidx-room-migration-nodeps:$(ROOM_LIBS_PATH)/room-migration-2.0.0-beta01.jar \
- bt-androidx-room-runtime-nodeps:$(ROOM_LIBS_PATH)/room-runtime-2.0.0-alpha1.aar \
- bt-androidx-room-testing-nodeps:$(ROOM_LIBS_PATH)/room-testing-2.0.0-alpha1.aar
-
-include $(BUILD_MULTI_PREBUILT)
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index 10c5d6d..eaf7fdc 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_STACK" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.MANAGE_USERS"/>
+ <uses-permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"/>
<uses-permission android:name="com.google.android.gallery3d.permission.GALLERY_PROVIDER"/>
<uses-permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER"/>
diff --git a/tests/unit/src/com/android/bluetooth/StateMachineTest.java b/tests/unit/src/com/android/bluetooth/StateMachineTest.java
new file mode 100644
index 0000000..5cf2255
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/StateMachineTest.java
@@ -0,0 +1,1985 @@
+/*
+ * Copyright (C) 2019 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.bluetooth;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import android.os.Debug;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.bluetooth.statemachine.State;
+import com.android.bluetooth.statemachine.StateMachine;
+import com.android.bluetooth.statemachine.StateMachine.LogRec;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for StateMachine.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class StateMachineTest {
+ private static final String ENTER = "enter";
+ private static final String EXIT = "exit";
+ private static final String ON_QUITTING = "ON_QUITTING";
+
+ private static final int TEST_CMD_1 = 1;
+ private static final int TEST_CMD_2 = 2;
+ private static final int TEST_CMD_3 = 3;
+ private static final int TEST_CMD_4 = 4;
+ private static final int TEST_CMD_5 = 5;
+ private static final int TEST_CMD_6 = 6;
+
+ private static final boolean DBG = true;
+ private static final boolean WAIT_FOR_DEBUGGER = false;
+ private static final String TAG = "StateMachineTest";
+
+ private void sleep(int millis) {
+ try {
+ Thread.sleep(millis);
+ } catch(InterruptedException e) {
+ }
+ }
+
+ private void dumpLogRecs(StateMachine sm) {
+ int size = sm.getLogRecSize();
+ tlog("size=" + size + " count=" + sm.getLogRecCount());
+ for (int i = 0; i < size; i++) {
+ LogRec lr = sm.getLogRec(i);
+ tlog(lr.toString());
+ }
+ }
+
+ private void dumpLogRecs(Collection<LogRec> clr) {
+ int size = clr.size();
+ tlog("size=" + size);
+ for (LogRec lr : clr) {
+ tlog(lr.toString());
+ }
+ }
+
+ /**
+ * Tests {@link StateMachine#toString()}.
+ */
+ class StateMachineToStringTest extends StateMachine {
+ StateMachineToStringTest(String name) {
+ super(name);
+ }
+ }
+
+ class ExampleState extends State {
+ String mName;
+
+ ExampleState(String name) {
+ mName = name;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+ }
+
+ @Test
+ public void testToStringSucceedsEvenIfMachineHasNoStates() throws Exception {
+ StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+ Assert.assertTrue(stateMachine.toString().contains("TestStateMachine"));
+ }
+
+ @Test
+ public void testToStringSucceedsEvenIfStateHasNoName() throws Exception {
+ StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+ State exampleState = new ExampleState(null);
+ stateMachine.addState(exampleState);
+ stateMachine.setInitialState(exampleState);
+ stateMachine.start();
+ Assert.assertTrue(stateMachine.toString().contains("TestStateMachine"));
+ Assert.assertTrue(stateMachine.toString().contains("(null)"));
+ }
+
+ @Test
+ public void testToStringIncludesMachineAndStateNames() throws Exception {
+ StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+ State exampleState = new ExampleState("exampleState");
+ stateMachine.addState(exampleState);
+ stateMachine.setInitialState(exampleState);
+ stateMachine.start();
+ Assert.assertTrue(stateMachine.toString().contains("TestStateMachine"));
+ Assert.assertTrue(stateMachine.toString().contains("exampleState"));
+ }
+
+ @Test
+ public void testToStringDoesNotContainMultipleLines() throws Exception {
+ StateMachine stateMachine = new StateMachineToStringTest("TestStateMachine");
+ State exampleState = new ExampleState("exampleState");
+ stateMachine.addState(exampleState);
+ stateMachine.setInitialState(exampleState);
+ stateMachine.start();
+ Assert.assertFalse(stateMachine.toString().contains("\n"));
+ }
+
+ /**
+ * Tests {@link StateMachine#quit()}.
+ */
+ class StateMachineQuitTest extends StateMachine {
+ Collection<LogRec> mLogRecs;
+
+ StateMachineQuitTest(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ @Override
+ public void onQuitting() {
+ tlog("onQuitting");
+ addLogRec(ON_QUITTING);
+ mLogRecs = mThisSm.copyLogRecs();
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ class S1 extends State {
+ @Override
+ public void exit() {
+ tlog("S1.exit");
+ addLogRec(EXIT);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ // Sleep and assume the other messages will be queued up.
+ case TEST_CMD_1: {
+ tlog("TEST_CMD_1");
+ sleep(500);
+ quit();
+ break;
+ }
+ default: {
+ tlog("default what=" + message.what);
+ break;
+ }
+ }
+ return HANDLED;
+ }
+ }
+
+ private StateMachineQuitTest mThisSm;
+ private S1 mS1 = new S1();
+ }
+
+ @Test
+ public void testStateMachineQuit() throws Exception {
+ if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+ StateMachineQuitTest smQuitTest = new StateMachineQuitTest("smQuitTest");
+ smQuitTest.start();
+ if (smQuitTest.isDbg()) tlog("testStateMachineQuit E");
+
+ synchronized (smQuitTest) {
+
+ // Send 6 message we'll quit on the first but all 6 should be processed before quitting.
+ for (int i = 1; i <= 6; i++) {
+ smQuitTest.sendMessage(smQuitTest.obtainMessage(i));
+ }
+
+ try {
+ // wait for the messages to be handled
+ smQuitTest.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachineQuit: exception while waiting " + e.getMessage());
+ }
+ }
+
+ dumpLogRecs(smQuitTest.mLogRecs);
+ Assert.assertEquals(8, smQuitTest.mLogRecs.size());
+
+ LogRec lr;
+ Iterator<LogRec> itr = smQuitTest.mLogRecs.iterator();
+ for (int i = 1; i <= 6; i++) {
+ lr = itr.next();
+ Assert.assertEquals(i, lr.getWhat());
+ Assert.assertEquals(smQuitTest.mS1, lr.getState());
+ Assert.assertEquals(smQuitTest.mS1, lr.getOriginalState());
+ }
+ lr = itr.next();
+ Assert.assertEquals(EXIT, lr.getInfo());
+ Assert.assertEquals(smQuitTest.mS1, lr.getState());
+
+ lr = itr.next();
+ Assert.assertEquals(ON_QUITTING, lr.getInfo());
+
+ if (smQuitTest.isDbg()) tlog("testStateMachineQuit X");
+ }
+
+ /**
+ * Tests {@link StateMachine#quitNow()}
+ */
+ class StateMachineQuitNowTest extends StateMachine {
+ public Collection<LogRec> mLogRecs = null;
+
+ StateMachineQuitNowTest(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ @Override
+ public void onQuitting() {
+ tlog("onQuitting");
+ addLogRec(ON_QUITTING);
+ // Get a copy of the log records since we're quitting and they will disappear
+ mLogRecs = mThisSm.copyLogRecs();
+
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ class S1 extends State {
+ @Override
+ public void exit() {
+ tlog("S1.exit");
+ addLogRec(EXIT);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ // Sleep and assume the other messages will be queued up.
+ case TEST_CMD_1: {
+ tlog("TEST_CMD_1");
+ sleep(500);
+ quitNow();
+ break;
+ }
+ default: {
+ tlog("default what=" + message.what);
+ break;
+ }
+ }
+ return HANDLED;
+ }
+ }
+
+ private StateMachineQuitNowTest mThisSm;
+ private S1 mS1 = new S1();
+ }
+
+ @Test
+ public void testStateMachineQuitNow() throws Exception {
+ if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+ StateMachineQuitNowTest smQuitNowTest = new StateMachineQuitNowTest("smQuitNowTest");
+ smQuitNowTest.start();
+ if (smQuitNowTest.isDbg()) tlog("testStateMachineQuitNow E");
+
+ synchronized (smQuitNowTest) {
+
+ // Send 6 message we'll QuitNow on the first even though
+ // we send 6 only one will be processed.
+ for (int i = 1; i <= 6; i++) {
+ smQuitNowTest.sendMessage(smQuitNowTest.obtainMessage(i));
+ }
+
+ try {
+ // wait for the messages to be handled
+ smQuitNowTest.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachineQuitNow: exception while waiting " + e.getMessage());
+ }
+ }
+
+ tlog("testStateMachineQuiteNow: logRecs=" + smQuitNowTest.mLogRecs);
+ Assert.assertEquals(3, smQuitNowTest.mLogRecs.size());
+
+ Iterator<LogRec> itr = smQuitNowTest.mLogRecs.iterator();
+ LogRec lr = itr.next();
+ Assert.assertEquals(1, lr.getWhat());
+ Assert.assertEquals(smQuitNowTest.mS1, lr.getState());
+ Assert.assertEquals(smQuitNowTest.mS1, lr.getOriginalState());
+
+ lr = itr.next();
+ Assert.assertEquals(EXIT, lr.getInfo());
+ Assert.assertEquals(smQuitNowTest.mS1, lr.getState());
+
+ lr = itr.next();
+ Assert.assertEquals(ON_QUITTING, lr.getInfo());
+
+ if (smQuitNowTest.isDbg()) tlog("testStateMachineQuitNow X");
+ }
+
+ /**
+ * Tests {@link StateMachine#quitNow()} immediately after {@link StateMachine#start()}.
+ */
+ class StateMachineQuitNowAfterStartTest extends StateMachine {
+ Collection<LogRec> mLogRecs;
+
+ StateMachineQuitNowAfterStartTest(String name, Looper looper) {
+ super(name, looper);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ @Override
+ public void onQuitting() {
+ tlog("onQuitting");
+ addLogRec(ON_QUITTING);
+ mLogRecs = mThisSm.copyLogRecs();
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ class S1 extends State {
+ @Override
+ public void enter() {
+ tlog("S1.enter");
+ addLogRec(ENTER);
+ }
+ @Override
+ public void exit() {
+ tlog("S1.exit");
+ addLogRec(EXIT);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ switch(message.what) {
+ // Sleep and assume the other messages will be queued up.
+ case TEST_CMD_1: {
+ tlog("TEST_CMD_1");
+ sleep(500);
+ break;
+ }
+ default: {
+ tlog("default what=" + message.what);
+ break;
+ }
+ }
+ return HANDLED;
+ }
+ }
+
+ private StateMachineQuitNowAfterStartTest mThisSm;
+ private S1 mS1 = new S1();
+ }
+
+ /**
+ * Test enter/exit can use transitionTo
+ */
+ class StateMachineEnterExitTransitionToTest extends StateMachine {
+
+ StateMachineEnterExitTransitionToTest(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+ addState(mS2);
+ addState(mS3);
+ addState(mS4);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ class S1 extends State {
+ @Override
+ public void enter() {
+ // Test transitions in enter on the initial state work
+ addLogRec(ENTER);
+ transitionTo(mS2);
+ tlog("S1.enter");
+ }
+ @Override
+ public void exit() {
+ addLogRec(EXIT);
+ tlog("S1.exit");
+ }
+ }
+
+ class S2 extends State {
+ @Override
+ public void enter() {
+ addLogRec(ENTER);
+ tlog("S2.enter");
+ }
+ @Override
+ public void exit() {
+ // Test transition in exit work
+ transitionTo(mS4);
+
+ Assert.assertEquals(TEST_CMD_1, getCurrentMessage().what);
+ addLogRec(EXIT);
+
+ tlog("S2.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ // Start a transition to S3 but it will be
+ // changed to a transition to S4 in exit
+ transitionTo(mS3);
+ tlog("S2.processMessage");
+ return HANDLED;
+ }
+ }
+
+ class S3 extends State {
+ @Override
+ public void enter() {
+ addLogRec(ENTER);
+ tlog("S3.enter");
+ }
+ @Override
+ public void exit() {
+ addLogRec(EXIT);
+ tlog("S3.exit");
+ }
+ }
+
+ class S4 extends State {
+ @Override
+ public void enter() {
+ addLogRec(ENTER);
+ // Test that we can do halting in an enter/exit
+ transitionToHaltingState();
+ tlog("S4.enter");
+ }
+ @Override
+ public void exit() {
+ addLogRec(EXIT);
+ tlog("S4.exit");
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachineEnterExitTransitionToTest mThisSm;
+ private S1 mS1 = new S1();
+ private S2 mS2 = new S2();
+ private S3 mS3 = new S3();
+ private S4 mS4 = new S4();
+ }
+
+ @Test
+ public void testStateMachineEnterExitTransitionToTest() throws Exception {
+ //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+ StateMachineEnterExitTransitionToTest smEnterExitTranstionToTest =
+ new StateMachineEnterExitTransitionToTest("smEnterExitTranstionToTest");
+ smEnterExitTranstionToTest.start();
+ if (smEnterExitTranstionToTest.isDbg()) {
+ tlog("testStateMachineEnterExitTransitionToTest E");
+ }
+
+ synchronized (smEnterExitTranstionToTest) {
+ smEnterExitTranstionToTest.sendMessage(TEST_CMD_1);
+
+ try {
+ // wait for the messages to be handled
+ smEnterExitTranstionToTest.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachineEnterExitTransitionToTest: exception while waiting "
+ + e.getMessage());
+ }
+ }
+
+ dumpLogRecs(smEnterExitTranstionToTest);
+
+ Assert.assertEquals(9, smEnterExitTranstionToTest.getLogRecCount());
+ LogRec lr;
+
+ lr = smEnterExitTranstionToTest.getLogRec(0);
+ Assert.assertEquals(ENTER, lr.getInfo());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS1, lr.getState());
+
+ lr = smEnterExitTranstionToTest.getLogRec(1);
+ Assert.assertEquals(EXIT, lr.getInfo());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS1, lr.getState());
+
+ lr = smEnterExitTranstionToTest.getLogRec(2);
+ Assert.assertEquals(ENTER, lr.getInfo());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getState());
+
+ lr = smEnterExitTranstionToTest.getLogRec(3);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getOriginalState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getDestState());
+
+ lr = smEnterExitTranstionToTest.getLogRec(4);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS2, lr.getOriginalState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState());
+ Assert.assertEquals(EXIT, lr.getInfo());
+
+ lr = smEnterExitTranstionToTest.getLogRec(5);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(ENTER, lr.getInfo());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getOriginalState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState());
+
+ lr = smEnterExitTranstionToTest.getLogRec(6);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(EXIT, lr.getInfo());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS3, lr.getOriginalState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState());
+
+ lr = smEnterExitTranstionToTest.getLogRec(7);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(ENTER, lr.getInfo());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getOriginalState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getDestState());
+
+ lr = smEnterExitTranstionToTest.getLogRec(8);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(EXIT, lr.getInfo());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getState());
+ Assert.assertEquals(smEnterExitTranstionToTest.mS4, lr.getOriginalState());
+
+ if (smEnterExitTranstionToTest.isDbg()) {
+ tlog("testStateMachineEnterExitTransitionToTest X");
+ }
+ }
+
+ /**
+ * Tests that ProcessedMessage works as a circular buffer.
+ */
+ class StateMachine0 extends StateMachine {
+ StateMachine0(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+ setLogRecSize(3);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ class S1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_6) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine0 mThisSm;
+ private S1 mS1 = new S1();
+ }
+
+ @Test
+ public void testStateMachine0() throws Exception {
+ //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+ StateMachine0 sm0 = new StateMachine0("sm0");
+ sm0.start();
+ if (sm0.isDbg()) tlog("testStateMachine0 E");
+
+ synchronized (sm0) {
+ // Send 6 messages
+ for (int i = 1; i <= 6; i++) {
+ sm0.sendMessage(sm0.obtainMessage(i));
+ }
+
+ try {
+ // wait for the messages to be handled
+ sm0.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachine0: exception while waiting " + e.getMessage());
+ }
+ }
+
+ Assert.assertEquals(6, sm0.getLogRecCount());
+ Assert.assertEquals(3, sm0.getLogRecSize());
+
+ dumpLogRecs(sm0);
+
+ LogRec lr;
+ lr = sm0.getLogRec(0);
+ Assert.assertEquals(TEST_CMD_4, lr.getWhat());
+ Assert.assertEquals(sm0.mS1, lr.getState());
+ Assert.assertEquals(sm0.mS1, lr.getOriginalState());
+
+ lr = sm0.getLogRec(1);
+ Assert.assertEquals(TEST_CMD_5, lr.getWhat());
+ Assert.assertEquals(sm0.mS1, lr.getState());
+ Assert.assertEquals(sm0.mS1, lr.getOriginalState());
+
+ lr = sm0.getLogRec(2);
+ Assert.assertEquals(TEST_CMD_6, lr.getWhat());
+ Assert.assertEquals(sm0.mS1, lr.getState());
+ Assert.assertEquals(sm0.mS1, lr.getOriginalState());
+
+ if (sm0.isDbg()) tlog("testStateMachine0 X");
+ }
+
+ /**
+ * This tests enter/exit and transitions to the same state.
+ * The state machine has one state, it receives two messages
+ * in state mS1. With the first message it transitions to
+ * itself which causes it to be exited and reentered.
+ */
+ class StateMachine1 extends StateMachine {
+ StateMachine1(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ if (DBG) tlog("StateMachine1: ctor X");
+ }
+
+ class S1 extends State {
+ @Override
+ public void enter() {
+ mEnterCount++;
+ }
+ @Override
+ public void exit() {
+ mExitCount++;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_1) {
+ Assert.assertEquals(1, mEnterCount);
+ Assert.assertEquals(0, mExitCount);
+ transitionTo(mS1);
+ } else if (message.what == TEST_CMD_2) {
+ Assert.assertEquals(2, mEnterCount);
+ Assert.assertEquals(1, mExitCount);
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine1 mThisSm;
+ private S1 mS1 = new S1();
+
+ private int mEnterCount;
+ private int mExitCount;
+ }
+
+ @Test @MediumTest
+ public void testStateMachine1() throws Exception {
+ StateMachine1 sm1 = new StateMachine1("sm1");
+ sm1.start();
+ if (sm1.isDbg()) tlog("testStateMachine1 E");
+
+ synchronized (sm1) {
+ // Send two messages
+ sm1.sendMessage(TEST_CMD_1);
+ sm1.sendMessage(TEST_CMD_2);
+
+ try {
+ // wait for the messages to be handled
+ sm1.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachine1: exception while waiting " + e.getMessage());
+ }
+ }
+
+ Assert.assertEquals(2, sm1.mEnterCount);
+ Assert.assertEquals(2, sm1.mExitCount);
+
+ Assert.assertEquals(2, sm1.getLogRecSize());
+
+ LogRec lr;
+ lr = sm1.getLogRec(0);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(sm1.mS1, lr.getState());
+ Assert.assertEquals(sm1.mS1, lr.getOriginalState());
+
+ lr = sm1.getLogRec(1);
+ Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+ Assert.assertEquals(sm1.mS1, lr.getState());
+ Assert.assertEquals(sm1.mS1, lr.getOriginalState());
+
+ Assert.assertEquals(2, sm1.mEnterCount);
+ Assert.assertEquals(2, sm1.mExitCount);
+
+ if (sm1.isDbg()) tlog("testStateMachine1 X");
+ }
+
+ /**
+ * Test deferring messages and states with no parents. The state machine
+ * has two states, it receives two messages in state mS1 deferring them
+ * until what == TEST_CMD_2 and then transitions to state mS2. State
+ * mS2 then receives both of the deferred messages first TEST_CMD_1 and
+ * then TEST_CMD_2.
+ */
+ class StateMachine2 extends StateMachine {
+ StateMachine2(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup the hierarchy
+ addState(mS1);
+ addState(mS2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ if (DBG) tlog("StateMachine2: ctor X");
+ }
+
+ class S1 extends State {
+ @Override
+ public void enter() {
+ mDidEnter = true;
+ }
+ @Override
+ public void exit() {
+ mDidExit = true;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ deferMessage(message);
+ if (message.what == TEST_CMD_2) {
+ transitionTo(mS2);
+ }
+ return HANDLED;
+ }
+ }
+
+ class S2 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine2 mThisSm;
+ private S1 mS1 = new S1();
+ private S2 mS2 = new S2();
+
+ private boolean mDidEnter = false;
+ private boolean mDidExit = false;
+ }
+
+ @Test @MediumTest
+ public void testStateMachine2() throws Exception {
+ StateMachine2 sm2 = new StateMachine2("sm2");
+ sm2.start();
+ if (sm2.isDbg()) tlog("testStateMachine2 E");
+
+ synchronized (sm2) {
+ // Send two messages
+ sm2.sendMessage(TEST_CMD_1);
+ sm2.sendMessage(TEST_CMD_2);
+
+ try {
+ // wait for the messages to be handled
+ sm2.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachine2: exception while waiting " + e.getMessage());
+ }
+ }
+
+ Assert.assertEquals(4, sm2.getLogRecSize());
+
+ LogRec lr;
+ lr = sm2.getLogRec(0);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(sm2.mS1, lr.getState());
+
+ lr = sm2.getLogRec(1);
+ Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+ Assert.assertEquals(sm2.mS1, lr.getState());
+
+ lr = sm2.getLogRec(2);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(sm2.mS2, lr.getState());
+
+ lr = sm2.getLogRec(3);
+ Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+ Assert.assertEquals(sm2.mS2, lr.getState());
+
+ Assert.assertTrue(sm2.mDidEnter);
+ Assert.assertTrue(sm2.mDidExit);
+
+ if (sm2.isDbg()) tlog("testStateMachine2 X");
+ }
+
+ /**
+ * Test that unhandled messages in a child are handled by the parent.
+ * When TEST_CMD_2 is received.
+ */
+ class StateMachine3 extends StateMachine {
+ StateMachine3(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup the simplest hierarchy of two states
+ // mParentState and mChildState.
+ // (Use indentation to help visualize hierarchy)
+ addState(mParentState);
+ addState(mChildState, mParentState);
+
+ // Set the initial state will be the child
+ setInitialState(mChildState);
+ if (DBG) tlog("StateMachine3: ctor X");
+ }
+
+ class ParentState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ class ChildState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine3 mThisSm;
+ private ParentState mParentState = new ParentState();
+ private ChildState mChildState = new ChildState();
+ }
+
+ @Test @MediumTest
+ public void testStateMachine3() throws Exception {
+ StateMachine3 sm3 = new StateMachine3("sm3");
+ sm3.start();
+ if (sm3.isDbg()) tlog("testStateMachine3 E");
+
+ synchronized (sm3) {
+ // Send two messages
+ sm3.sendMessage(TEST_CMD_1);
+ sm3.sendMessage(TEST_CMD_2);
+
+ try {
+ // wait for the messages to be handled
+ sm3.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachine3: exception while waiting " + e.getMessage());
+ }
+ }
+
+ Assert.assertEquals(2, sm3.getLogRecSize());
+
+ LogRec lr;
+ lr = sm3.getLogRec(0);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(sm3.mParentState, lr.getState());
+ Assert.assertEquals(sm3.mChildState, lr.getOriginalState());
+
+ lr = sm3.getLogRec(1);
+ Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+ Assert.assertEquals(sm3.mParentState, lr.getState());
+ Assert.assertEquals(sm3.mChildState, lr.getOriginalState());
+
+ if (sm3.isDbg()) tlog("testStateMachine3 X");
+ }
+
+ /**
+ * Test a hierarchy of 3 states a parent and two children
+ * with transition from child 1 to child 2 and child 2
+ * lets the parent handle the messages.
+ */
+ class StateMachine4 extends StateMachine {
+ StateMachine4(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup a hierarchy of three states
+ // mParentState, mChildState1 & mChildState2
+ // (Use indentation to help visualize hierarchy)
+ addState(mParentState);
+ addState(mChildState1, mParentState);
+ addState(mChildState2, mParentState);
+
+ // Set the initial state will be child 1
+ setInitialState(mChildState1);
+ if (DBG) tlog("StateMachine4: ctor X");
+ }
+
+ class ParentState extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ class ChildState1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ transitionTo(mChildState2);
+ return HANDLED;
+ }
+ }
+
+ class ChildState2 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine4 mThisSm;
+ private ParentState mParentState = new ParentState();
+ private ChildState1 mChildState1 = new ChildState1();
+ private ChildState2 mChildState2 = new ChildState2();
+ }
+
+ @Test @MediumTest
+ public void testStateMachine4() throws Exception {
+ StateMachine4 sm4 = new StateMachine4("sm4");
+ sm4.start();
+ if (sm4.isDbg()) tlog("testStateMachine4 E");
+
+ synchronized (sm4) {
+ // Send two messages
+ sm4.sendMessage(TEST_CMD_1);
+ sm4.sendMessage(TEST_CMD_2);
+
+ try {
+ // wait for the messages to be handled
+ sm4.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachine4: exception while waiting " + e.getMessage());
+ }
+ }
+
+
+ Assert.assertEquals(2, sm4.getLogRecSize());
+
+ LogRec lr;
+ lr = sm4.getLogRec(0);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(sm4.mChildState1, lr.getState());
+ Assert.assertEquals(sm4.mChildState1, lr.getOriginalState());
+
+ lr = sm4.getLogRec(1);
+ Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+ Assert.assertEquals(sm4.mParentState, lr.getState());
+ Assert.assertEquals(sm4.mChildState2, lr.getOriginalState());
+
+ if (sm4.isDbg()) tlog("testStateMachine4 X");
+ }
+
+ /**
+ * Test transition from one child to another of a "complex"
+ * hierarchy with two parents and multiple children.
+ */
+ class StateMachine5 extends StateMachine {
+ StateMachine5(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup a hierarchy with two parents and some children.
+ // (Use indentation to help visualize hierarchy)
+ addState(mParentState1);
+ addState(mChildState1, mParentState1);
+ addState(mChildState2, mParentState1);
+
+ addState(mParentState2);
+ addState(mChildState3, mParentState2);
+ addState(mChildState4, mParentState2);
+ addState(mChildState5, mChildState4);
+
+ // Set the initial state will be the child
+ setInitialState(mChildState1);
+ if (DBG) tlog("StateMachine5: ctor X");
+ }
+
+ class ParentState1 extends State {
+ @Override
+ public void enter() {
+ mParentState1EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mParentState1ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ return HANDLED;
+ }
+ }
+
+ class ChildState1 extends State {
+ @Override
+ public void enter() {
+ mChildState1EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState1ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ Assert.assertEquals(1, mParentState1EnterCount);
+ Assert.assertEquals(0, mParentState1ExitCount);
+ Assert.assertEquals(1, mChildState1EnterCount);
+ Assert.assertEquals(0, mChildState1ExitCount);
+ Assert.assertEquals(0, mChildState2EnterCount);
+ Assert.assertEquals(0, mChildState2ExitCount);
+ Assert.assertEquals(0, mParentState2EnterCount);
+ Assert.assertEquals(0, mParentState2ExitCount);
+ Assert.assertEquals(0, mChildState3EnterCount);
+ Assert.assertEquals(0, mChildState3ExitCount);
+ Assert.assertEquals(0, mChildState4EnterCount);
+ Assert.assertEquals(0, mChildState4ExitCount);
+ Assert.assertEquals(0, mChildState5EnterCount);
+ Assert.assertEquals(0, mChildState5ExitCount);
+
+ transitionTo(mChildState2);
+ return HANDLED;
+ }
+ }
+
+ class ChildState2 extends State {
+ @Override
+ public void enter() {
+ mChildState2EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState2ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ Assert.assertEquals(1, mParentState1EnterCount);
+ Assert.assertEquals(0, mParentState1ExitCount);
+ Assert.assertEquals(1, mChildState1EnterCount);
+ Assert.assertEquals(1, mChildState1ExitCount);
+ Assert.assertEquals(1, mChildState2EnterCount);
+ Assert.assertEquals(0, mChildState2ExitCount);
+ Assert.assertEquals(0, mParentState2EnterCount);
+ Assert.assertEquals(0, mParentState2ExitCount);
+ Assert.assertEquals(0, mChildState3EnterCount);
+ Assert.assertEquals(0, mChildState3ExitCount);
+ Assert.assertEquals(0, mChildState4EnterCount);
+ Assert.assertEquals(0, mChildState4ExitCount);
+ Assert.assertEquals(0, mChildState5EnterCount);
+ Assert.assertEquals(0, mChildState5ExitCount);
+
+ transitionTo(mChildState5);
+ return HANDLED;
+ }
+ }
+
+ class ParentState2 extends State {
+ @Override
+ public void enter() {
+ mParentState2EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mParentState2ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ Assert.assertEquals(1, mParentState1EnterCount);
+ Assert.assertEquals(1, mParentState1ExitCount);
+ Assert.assertEquals(1, mChildState1EnterCount);
+ Assert.assertEquals(1, mChildState1ExitCount);
+ Assert.assertEquals(1, mChildState2EnterCount);
+ Assert.assertEquals(1, mChildState2ExitCount);
+ Assert.assertEquals(2, mParentState2EnterCount);
+ Assert.assertEquals(1, mParentState2ExitCount);
+ Assert.assertEquals(1, mChildState3EnterCount);
+ Assert.assertEquals(1, mChildState3ExitCount);
+ Assert.assertEquals(2, mChildState4EnterCount);
+ Assert.assertEquals(2, mChildState4ExitCount);
+ Assert.assertEquals(1, mChildState5EnterCount);
+ Assert.assertEquals(1, mChildState5ExitCount);
+
+ transitionToHaltingState();
+ return HANDLED;
+ }
+ }
+
+ class ChildState3 extends State {
+ @Override
+ public void enter() {
+ mChildState3EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState3ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ Assert.assertEquals(1, mParentState1EnterCount);
+ Assert.assertEquals(1, mParentState1ExitCount);
+ Assert.assertEquals(1, mChildState1EnterCount);
+ Assert.assertEquals(1, mChildState1ExitCount);
+ Assert.assertEquals(1, mChildState2EnterCount);
+ Assert.assertEquals(1, mChildState2ExitCount);
+ Assert.assertEquals(1, mParentState2EnterCount);
+ Assert.assertEquals(0, mParentState2ExitCount);
+ Assert.assertEquals(1, mChildState3EnterCount);
+ Assert.assertEquals(0, mChildState3ExitCount);
+ Assert.assertEquals(1, mChildState4EnterCount);
+ Assert.assertEquals(1, mChildState4ExitCount);
+ Assert.assertEquals(1, mChildState5EnterCount);
+ Assert.assertEquals(1, mChildState5ExitCount);
+
+ transitionTo(mChildState4);
+ return HANDLED;
+ }
+ }
+
+ class ChildState4 extends State {
+ @Override
+ public void enter() {
+ mChildState4EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState4ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ Assert.assertEquals(1, mParentState1EnterCount);
+ Assert.assertEquals(1, mParentState1ExitCount);
+ Assert.assertEquals(1, mChildState1EnterCount);
+ Assert.assertEquals(1, mChildState1ExitCount);
+ Assert.assertEquals(1, mChildState2EnterCount);
+ Assert.assertEquals(1, mChildState2ExitCount);
+ Assert.assertEquals(1, mParentState2EnterCount);
+ Assert.assertEquals(0, mParentState2ExitCount);
+ Assert.assertEquals(1, mChildState3EnterCount);
+ Assert.assertEquals(1, mChildState3ExitCount);
+ Assert.assertEquals(2, mChildState4EnterCount);
+ Assert.assertEquals(1, mChildState4ExitCount);
+ Assert.assertEquals(1, mChildState5EnterCount);
+ Assert.assertEquals(1, mChildState5ExitCount);
+
+ transitionTo(mParentState2);
+ return HANDLED;
+ }
+ }
+
+ class ChildState5 extends State {
+ @Override
+ public void enter() {
+ mChildState5EnterCount += 1;
+ }
+ @Override
+ public void exit() {
+ mChildState5ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ Assert.assertEquals(1, mParentState1EnterCount);
+ Assert.assertEquals(1, mParentState1ExitCount);
+ Assert.assertEquals(1, mChildState1EnterCount);
+ Assert.assertEquals(1, mChildState1ExitCount);
+ Assert.assertEquals(1, mChildState2EnterCount);
+ Assert.assertEquals(1, mChildState2ExitCount);
+ Assert.assertEquals(1, mParentState2EnterCount);
+ Assert.assertEquals(0, mParentState2ExitCount);
+ Assert.assertEquals(0, mChildState3EnterCount);
+ Assert.assertEquals(0, mChildState3ExitCount);
+ Assert.assertEquals(1, mChildState4EnterCount);
+ Assert.assertEquals(0, mChildState4ExitCount);
+ Assert.assertEquals(1, mChildState5EnterCount);
+ Assert.assertEquals(0, mChildState5ExitCount);
+
+ transitionTo(mChildState3);
+ return HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine5 mThisSm;
+ private ParentState1 mParentState1 = new ParentState1();
+ private ChildState1 mChildState1 = new ChildState1();
+ private ChildState2 mChildState2 = new ChildState2();
+ private ParentState2 mParentState2 = new ParentState2();
+ private ChildState3 mChildState3 = new ChildState3();
+ private ChildState4 mChildState4 = new ChildState4();
+ private ChildState5 mChildState5 = new ChildState5();
+
+ private int mParentState1EnterCount = 0;
+ private int mParentState1ExitCount = 0;
+ private int mChildState1EnterCount = 0;
+ private int mChildState1ExitCount = 0;
+ private int mChildState2EnterCount = 0;
+ private int mChildState2ExitCount = 0;
+ private int mParentState2EnterCount = 0;
+ private int mParentState2ExitCount = 0;
+ private int mChildState3EnterCount = 0;
+ private int mChildState3ExitCount = 0;
+ private int mChildState4EnterCount = 0;
+ private int mChildState4ExitCount = 0;
+ private int mChildState5EnterCount = 0;
+ private int mChildState5ExitCount = 0;
+ }
+
+ @Test @MediumTest
+ public void testStateMachine5() throws Exception {
+ StateMachine5 sm5 = new StateMachine5("sm5");
+ sm5.start();
+ if (sm5.isDbg()) tlog("testStateMachine5 E");
+
+ synchronized (sm5) {
+ // Send 6 messages
+ sm5.sendMessage(TEST_CMD_1);
+ sm5.sendMessage(TEST_CMD_2);
+ sm5.sendMessage(TEST_CMD_3);
+ sm5.sendMessage(TEST_CMD_4);
+ sm5.sendMessage(TEST_CMD_5);
+ sm5.sendMessage(TEST_CMD_6);
+
+ try {
+ // wait for the messages to be handled
+ sm5.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachine5: exception while waiting " + e.getMessage());
+ }
+ }
+
+
+ Assert.assertEquals(6, sm5.getLogRecSize());
+
+ Assert.assertEquals(1, sm5.mParentState1EnterCount);
+ Assert.assertEquals(1, sm5.mParentState1ExitCount);
+ Assert.assertEquals(1, sm5.mChildState1EnterCount);
+ Assert.assertEquals(1, sm5.mChildState1ExitCount);
+ Assert.assertEquals(1, sm5.mChildState2EnterCount);
+ Assert.assertEquals(1, sm5.mChildState2ExitCount);
+ Assert.assertEquals(2, sm5.mParentState2EnterCount);
+ Assert.assertEquals(2, sm5.mParentState2ExitCount);
+ Assert.assertEquals(1, sm5.mChildState3EnterCount);
+ Assert.assertEquals(1, sm5.mChildState3ExitCount);
+ Assert.assertEquals(2, sm5.mChildState4EnterCount);
+ Assert.assertEquals(2, sm5.mChildState4ExitCount);
+ Assert.assertEquals(1, sm5.mChildState5EnterCount);
+ Assert.assertEquals(1, sm5.mChildState5ExitCount);
+
+ LogRec lr;
+ lr = sm5.getLogRec(0);
+ Assert.assertEquals(TEST_CMD_1, lr.getWhat());
+ Assert.assertEquals(sm5.mChildState1, lr.getState());
+ Assert.assertEquals(sm5.mChildState1, lr.getOriginalState());
+
+ lr = sm5.getLogRec(1);
+ Assert.assertEquals(TEST_CMD_2, lr.getWhat());
+ Assert.assertEquals(sm5.mChildState2, lr.getState());
+ Assert.assertEquals(sm5.mChildState2, lr.getOriginalState());
+
+ lr = sm5.getLogRec(2);
+ Assert.assertEquals(TEST_CMD_3, lr.getWhat());
+ Assert.assertEquals(sm5.mChildState5, lr.getState());
+ Assert.assertEquals(sm5.mChildState5, lr.getOriginalState());
+
+ lr = sm5.getLogRec(3);
+ Assert.assertEquals(TEST_CMD_4, lr.getWhat());
+ Assert.assertEquals(sm5.mChildState3, lr.getState());
+ Assert.assertEquals(sm5.mChildState3, lr.getOriginalState());
+
+ lr = sm5.getLogRec(4);
+ Assert.assertEquals(TEST_CMD_5, lr.getWhat());
+ Assert.assertEquals(sm5.mChildState4, lr.getState());
+ Assert.assertEquals(sm5.mChildState4, lr.getOriginalState());
+
+ lr = sm5.getLogRec(5);
+ Assert.assertEquals(TEST_CMD_6, lr.getWhat());
+ Assert.assertEquals(sm5.mParentState2, lr.getState());
+ Assert.assertEquals(sm5.mParentState2, lr.getOriginalState());
+
+ if (sm5.isDbg()) tlog("testStateMachine5 X");
+ }
+
+ /**
+ * Test that the initial state enter is invoked immediately
+ * after construction and before any other messages arrive and that
+ * sendMessageDelayed works.
+ */
+ class StateMachine6 extends StateMachine {
+ StateMachine6(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ if (DBG) tlog("StateMachine6: ctor X");
+ }
+
+ class S1 extends State {
+ @Override
+ public void enter() {
+ sendMessage(TEST_CMD_1);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_1) {
+ mArrivalTimeMsg1 = SystemClock.elapsedRealtime();
+ } else if (message.what == TEST_CMD_2) {
+ mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine6 mThisSm;
+ private S1 mS1 = new S1();
+
+ private long mArrivalTimeMsg1;
+ private long mArrivalTimeMsg2;
+ }
+
+ @Test @MediumTest
+ public void testStateMachine6() throws Exception {
+ final int DELAY_TIME = 250;
+ final int DELAY_FUDGE = 20;
+
+ StateMachine6 sm6 = new StateMachine6("sm6");
+ sm6.start();
+ if (sm6.isDbg()) tlog("testStateMachine6 E");
+
+ synchronized (sm6) {
+ // Send a message
+ sm6.sendMessageDelayed(TEST_CMD_2, DELAY_TIME);
+
+ try {
+ // wait for the messages to be handled
+ sm6.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachine6: exception while waiting " + e.getMessage());
+ }
+ }
+
+ /**
+ * TEST_CMD_1 was sent in enter and must always have been processed
+ * immediately after construction and hence the arrival time difference
+ * should always >= to the DELAY_TIME
+ */
+ long arrivalTimeDiff = sm6.mArrivalTimeMsg2 - sm6.mArrivalTimeMsg1;
+ long expectedDelay = DELAY_TIME - DELAY_FUDGE;
+ if (sm6.isDbg()) tlog("testStateMachine6: expect " + arrivalTimeDiff
+ + " >= " + expectedDelay);
+ Assert.assertTrue(arrivalTimeDiff >= expectedDelay);
+
+ if (sm6.isDbg()) tlog("testStateMachine6 X");
+ }
+
+ /**
+ * Test that enter is invoked immediately after exit. This validates
+ * that enter can be used to send a watch dog message for its state.
+ */
+ class StateMachine7 extends StateMachine {
+ private final int SM7_DELAY_TIME = 250;
+
+ StateMachine7(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+ addState(mS2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ if (DBG) tlog("StateMachine7: ctor X");
+ }
+
+ class S1 extends State {
+ @Override
+ public void exit() {
+ sendMessage(TEST_CMD_2);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ transitionTo(mS2);
+ return HANDLED;
+ }
+ }
+
+ class S2 extends State {
+ @Override
+ public void enter() {
+ // Send a delayed message as a watch dog
+ sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME);
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ mMsgCount += 1;
+ mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
+ } else if (message.what == TEST_CMD_3) {
+ mMsgCount += 1;
+ mArrivalTimeMsg3 = SystemClock.elapsedRealtime();
+ }
+
+ if (mMsgCount == 2) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine7 mThisSm;
+ private S1 mS1 = new S1();
+ private S2 mS2 = new S2();
+
+ private int mMsgCount = 0;
+ private long mArrivalTimeMsg2;
+ private long mArrivalTimeMsg3;
+ }
+
+ @Test @MediumTest
+ public void testStateMachine7() throws Exception {
+ final int SM7_DELAY_FUDGE = 20;
+
+ StateMachine7 sm7 = new StateMachine7("sm7");
+ sm7.start();
+ if (sm7.isDbg()) tlog("testStateMachine7 E");
+
+ synchronized (sm7) {
+ // Send a message
+ sm7.sendMessage(TEST_CMD_1);
+
+ try {
+ // wait for the messages to be handled
+ sm7.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachine7: exception while waiting " + e.getMessage());
+ }
+ }
+
+ /**
+ * TEST_CMD_3 was sent in S2.enter with a delay and must always have been
+ * processed immediately after S1.exit. Since S1.exit sent TEST_CMD_2
+ * without a delay the arrival time difference should always >= to SM7_DELAY_TIME.
+ */
+ long arrivalTimeDiff = sm7.mArrivalTimeMsg3 - sm7.mArrivalTimeMsg2;
+ long expectedDelay = sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE;
+ if (sm7.isDbg()) tlog("testStateMachine7: expect " + arrivalTimeDiff
+ + " >= " + expectedDelay);
+ Assert.assertTrue(arrivalTimeDiff >= expectedDelay);
+
+ if (sm7.isDbg()) tlog("testStateMachine7 X");
+ }
+
+ /**
+ * Test unhandledMessage.
+ */
+ class StateMachineUnhandledMessage extends StateMachine {
+ StateMachineUnhandledMessage(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+ @Override
+ public void unhandledMessage(Message message) {
+ mUnhandledMessageCount += 1;
+ }
+
+ class S1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachineUnhandledMessage mThisSm;
+ private int mUnhandledMessageCount;
+ private S1 mS1 = new S1();
+ }
+
+ @Test
+ public void testStateMachineUnhandledMessage() throws Exception {
+
+ StateMachineUnhandledMessage sm = new StateMachineUnhandledMessage("smUnhandledMessage");
+ sm.start();
+ if (sm.isDbg()) tlog("testStateMachineUnhandledMessage E");
+
+ synchronized (sm) {
+ // Send 2 messages
+ for (int i = 1; i <= 2; i++) {
+ sm.sendMessage(i);
+ }
+
+ try {
+ // wait for the messages to be handled
+ sm.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachineUnhandledMessage: exception while waiting "
+ + e.getMessage());
+ }
+ }
+
+ Assert.assertEquals(2, sm.getLogRecSize());
+ Assert.assertEquals(2, sm.mUnhandledMessageCount);
+
+ if (sm.isDbg()) tlog("testStateMachineUnhandledMessage X");
+ }
+
+ /**
+ * Test state machines sharing the same thread/looper. Multiple instances
+ * of the same state machine will be created. They will all share the
+ * same thread and thus each can update <code>sharedCounter</code> which
+ * will be used to notify testStateMachineSharedThread that the test is
+ * complete.
+ */
+ class StateMachineSharedThread extends StateMachine {
+ StateMachineSharedThread(String name, Looper looper, int maxCount) {
+ super(name, looper);
+ mMaxCount = maxCount;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ class S1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_4) {
+ transitionToHaltingState();
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ // Update the shared counter, which is OK since all state
+ // machines are using the same thread.
+ sharedCounter += 1;
+ if (sharedCounter == mMaxCount) {
+ synchronized (waitObject) {
+ waitObject.notifyAll();
+ }
+ }
+ }
+
+ private int mMaxCount;
+ private S1 mS1 = new S1();
+ }
+ private static int sharedCounter = 0;
+ private static Object waitObject = new Object();
+
+ @Test @MediumTest
+ public void testStateMachineSharedThread() throws Exception {
+ if (DBG) tlog("testStateMachineSharedThread E");
+
+ // Create and start the handler thread
+ HandlerThread smThread = new HandlerThread("testStateMachineSharedThread");
+ smThread.start();
+
+ // Create the state machines
+ StateMachineSharedThread sms[] = new StateMachineSharedThread[10];
+ for (int i = 0; i < sms.length; i++) {
+ sms[i] = new StateMachineSharedThread("smSharedThread",
+ smThread.getLooper(), sms.length);
+ sms[i].start();
+ }
+
+ synchronized (waitObject) {
+ // Send messages to each of the state machines
+ for (StateMachineSharedThread sm : sms) {
+ for (int i = 1; i <= 4; i++) {
+ sm.sendMessage(i);
+ }
+ }
+
+ // Wait for the last state machine to notify its done
+ try {
+ waitObject.wait();
+ } catch (InterruptedException e) {
+ tloge("testStateMachineSharedThread: exception while waiting "
+ + e.getMessage());
+ }
+ }
+
+ for (StateMachineSharedThread sm : sms) {
+ Assert.assertEquals(4, sm.getLogRecCount());
+ for (int i = 0; i < sm.getLogRecSize(); i++) {
+ LogRec lr = sm.getLogRec(i);
+ Assert.assertEquals(i+1, lr.getWhat());
+ Assert.assertEquals(sm.mS1, lr.getState());
+ Assert.assertEquals(sm.mS1, lr.getOriginalState());
+ }
+ }
+
+ if (DBG) tlog("testStateMachineSharedThread X");
+ }
+
+ static class Hsm1 extends StateMachine {
+ private static final String HSM1_TAG = "hsm1";
+
+ public static final int CMD_1 = 1;
+ public static final int CMD_2 = 2;
+ public static final int CMD_3 = 3;
+ public static final int CMD_4 = 4;
+ public static final int CMD_5 = 5;
+
+ public static Hsm1 makeHsm1() {
+ Log.d(HSM1_TAG, "makeHsm1 E");
+ Hsm1 sm = new Hsm1(HSM1_TAG);
+ sm.start();
+ Log.d(HSM1_TAG, "makeHsm1 X");
+ return sm;
+ }
+
+ Hsm1(String name) {
+ super(name);
+ tlog("ctor E");
+
+ // Add states, use indentation to show hierarchy
+ addState(mP1);
+ addState(mS1, mP1);
+ addState(mS2, mP1);
+ addState(mP2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ tlog("ctor X");
+ }
+
+ class P1 extends State {
+ @Override
+ public void enter() {
+ tlog("P1.enter");
+ }
+ @Override
+ public void exit() {
+ tlog("P1.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ boolean retVal;
+ tlog("P1.processMessage what=" + message.what);
+ switch(message.what) {
+ case CMD_2:
+ // CMD_2 will arrive in mS2 before CMD_3
+ sendMessage(CMD_3);
+ deferMessage(message);
+ transitionTo(mS2);
+ retVal = true;
+ break;
+ default:
+ // Any message we don't understand in this state invokes unhandledMessage
+ retVal = false;
+ break;
+ }
+ return retVal;
+ }
+ }
+
+ class S1 extends State {
+ @Override
+ public void enter() {
+ tlog("S1.enter");
+ }
+ @Override
+ public void exit() {
+ tlog("S1.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ tlog("S1.processMessage what=" + message.what);
+ if (message.what == CMD_1) {
+ // Transition to ourself to show that enter/exit is called
+ transitionTo(mS1);
+ return HANDLED;
+ } else {
+ // Let parent process all other messages
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class S2 extends State {
+ @Override
+ public void enter() {
+ tlog("S2.enter");
+ }
+ @Override
+ public void exit() {
+ tlog("S2.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ boolean retVal;
+ tlog("S2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_2):
+ sendMessage(CMD_4);
+ retVal = true;
+ break;
+ case(CMD_3):
+ deferMessage(message);
+ transitionTo(mP2);
+ retVal = true;
+ break;
+ default:
+ retVal = false;
+ break;
+ }
+ return retVal;
+ }
+ }
+
+ class P2 extends State {
+ @Override
+ public void enter() {
+ tlog("P2.enter");
+ sendMessage(CMD_5);
+ }
+ @Override
+ public void exit() {
+ tlog("P2.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ tlog("P2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_3):
+ break;
+ case(CMD_4):
+ break;
+ case(CMD_5):
+ transitionToHaltingState();
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ @Override
+ protected void onHalting() {
+ tlog("halting");
+ synchronized (this) {
+ this.notifyAll();
+ }
+ }
+
+ P1 mP1 = new P1();
+ S1 mS1 = new S1();
+ S2 mS2 = new S2();
+ P2 mP2 = new P2();
+ }
+
+ @Test @MediumTest
+ public void testHsm1() throws Exception {
+ if (DBG) tlog("testHsm1 E");
+
+ Hsm1 sm = Hsm1.makeHsm1();
+
+ // Send messages
+ sm.sendMessage(Hsm1.CMD_1);
+ sm.sendMessage(Hsm1.CMD_2);
+
+ synchronized (sm) {
+ // Wait for the last state machine to notify its done
+ try {
+ sm.wait();
+ } catch (InterruptedException e) {
+ tloge("testHsm1: exception while waiting " + e.getMessage());
+ }
+ }
+
+ dumpLogRecs(sm);
+
+ Assert.assertEquals(7, sm.getLogRecCount());
+
+ LogRec lr = sm.getLogRec(0);
+ Assert.assertEquals(Hsm1.CMD_1, lr.getWhat());
+ Assert.assertEquals(sm.mS1, lr.getState());
+ Assert.assertEquals(sm.mS1, lr.getOriginalState());
+
+ lr = sm.getLogRec(1);
+ Assert.assertEquals(Hsm1.CMD_2, lr.getWhat());
+ Assert.assertEquals(sm.mP1, lr.getState());
+ Assert.assertEquals(sm.mS1, lr.getOriginalState());
+
+ lr = sm.getLogRec(2);
+ Assert.assertEquals(Hsm1.CMD_2, lr.getWhat());
+ Assert.assertEquals(sm.mS2, lr.getState());
+ Assert.assertEquals(sm.mS2, lr.getOriginalState());
+
+ lr = sm.getLogRec(3);
+ Assert.assertEquals(Hsm1.CMD_3, lr.getWhat());
+ Assert.assertEquals(sm.mS2, lr.getState());
+ Assert.assertEquals(sm.mS2, lr.getOriginalState());
+
+ lr = sm.getLogRec(4);
+ Assert.assertEquals(Hsm1.CMD_3, lr.getWhat());
+ Assert.assertEquals(sm.mP2, lr.getState());
+ Assert.assertEquals(sm.mP2, lr.getOriginalState());
+
+ lr = sm.getLogRec(5);
+ Assert.assertEquals(Hsm1.CMD_4, lr.getWhat());
+ Assert.assertEquals(sm.mP2, lr.getState());
+ Assert.assertEquals(sm.mP2, lr.getOriginalState());
+
+ lr = sm.getLogRec(6);
+ Assert.assertEquals(Hsm1.CMD_5, lr.getWhat());
+ Assert.assertEquals(sm.mP2, lr.getState());
+ Assert.assertEquals(sm.mP2, lr.getOriginalState());
+
+ if (DBG) tlog("testStateMachineSharedThread X");
+ }
+
+ private static void tlog(String s) {
+ Log.d(TAG, s);
+ }
+
+ private static void tloge(String s) {
+ Log.e(TAG, s);
+ }
+}
diff --git a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index 4492a45..99403eb 100644
--- a/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -115,7 +115,7 @@
mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
- doReturn(new ParcelUuid[]{BluetoothUuid.AudioSink}).when(mAdapterService)
+ doReturn(new ParcelUuid[]{BluetoothUuid.A2DP_SINK}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
}
@@ -260,32 +260,25 @@
@Test
public void testGetPriority() {
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
Assert.assertEquals("Initial device priority",
- BluetoothProfile.PRIORITY_UNDEFINED,
- mA2dpService.getPriority(mTestDevice));
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+ mA2dpService.getConnectionPolicy(mTestDevice));
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
Assert.assertEquals("Setting device priority to PRIORITY_OFF",
- BluetoothProfile.PRIORITY_OFF,
- mA2dpService.getPriority(mTestDevice));
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+ mA2dpService.getConnectionPolicy(mTestDevice));
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Assert.assertEquals("Setting device priority to PRIORITY_ON",
- BluetoothProfile.PRIORITY_ON,
- mA2dpService.getPriority(mTestDevice));
-
- when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
- Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT",
- BluetoothProfile.PRIORITY_AUTO_CONNECT,
- mA2dpService.getPriority(mTestDevice));
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+ mA2dpService.getConnectionPolicy(mTestDevice));
}
/**
@@ -296,43 +289,35 @@
int badPriorityValue = 1024;
int badBondState = 42;
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_NONE, badPriorityValue, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_BONDING, badPriorityValue, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_BONDED, badPriorityValue, false);
testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_OFF, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mTestDevice,
badBondState, badPriorityValue, false);
}
@@ -345,13 +330,13 @@
public void testOutgoingConnectMissingAudioSinkUuid() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
// Return AudioSource UUID instead of AudioSink
- doReturn(new ParcelUuid[]{BluetoothUuid.AudioSource}).when(mAdapterService)
+ doReturn(new ParcelUuid[]{BluetoothUuid.A2DP_SOURCE}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// Send a connect request
@@ -368,8 +353,8 @@
// Set the device priority to PRIORITY_OFF so connect() should fail
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Send a connect request
Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice));
@@ -382,8 +367,8 @@
public void testOutgoingConnectTimeout() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -413,8 +398,8 @@
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -484,8 +469,8 @@
BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i);
testDevices[i] = testDevice;
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(testDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(testDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// Send a connect request
Assert.assertTrue("Connect failed", mA2dpService.connect(testDevice));
// Verify the connection state broadcast, and that we are in Connecting state
@@ -512,8 +497,8 @@
// Prepare and connect the extra test device. The connect request should fail
extraTestDevice = TestUtils.getTestDevice(mAdapter, MAX_CONNECTED_AUDIO_DEVICES);
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(extraTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(extraTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// Send a connect request
Assert.assertFalse("Connect expected to fail", mA2dpService.connect(extraTestDevice));
}
@@ -528,8 +513,8 @@
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -608,8 +593,8 @@
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -673,8 +658,8 @@
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -732,8 +717,8 @@
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(any(BluetoothDevice.class));
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(any(BluetoothDevice.class));
@@ -906,8 +891,8 @@
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mA2dpNativeInterface).connectA2dp(device);
doReturn(true).when(mA2dpNativeInterface).disconnectA2dp(device);
doReturn(true).when(mA2dpNativeInterface).setCodecConfigPreference(
@@ -1026,7 +1011,7 @@
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP))
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP))
.thenReturn(priority);
// Test when the AdapterService is in non-quiet mode: the result should not depend
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
index d82d28b..c089dd3 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
@@ -92,7 +92,7 @@
* @param priority - The priority value you want the device to have
*/
private void mockDevicePriority(BluetoothDevice device, int priority) {
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.A2DP_SINK))
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP_SINK))
.thenReturn(priority);
}
@@ -107,7 +107,7 @@
@Test
public void testConnect() {
BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
- mockDevicePriority(device, BluetoothProfile.PRIORITY_ON);
+ mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Assert.assertTrue(mService.connect(device));
}
@@ -117,7 +117,7 @@
@Test
public void testConnectPriorityOffDevice() {
BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
- mockDevicePriority(device, BluetoothProfile.PRIORITY_OFF);
+ mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
Assert.assertFalse(mService.connect(device));
}
}
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
index c759a8a..b8fbbb9 100644
--- a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -195,6 +195,21 @@
}
@Test
+ public void testFocusRerequest() {
+ // Focus was lost transiently, expect streaming to stop.
+ testSnkPlay();
+ mStreamHandler.handleMessage(
+ mStreamHandler.obtainMessage(A2dpSinkStreamHandler.AUDIO_FOCUS_CHANGE,
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT));
+ verify(mMockAudioManager, times(0)).abandonAudioFocus(any());
+ verify(mMockA2dpSink, times(0)).informAudioFocusStateNative(0);
+ verify(mMockA2dpSink, times(1)).informAudioTrackGainNative(0);
+ mStreamHandler.handleMessage(
+ mStreamHandler.obtainMessage(A2dpSinkStreamHandler.REQUEST_FOCUS, true));
+ verify(mMockAudioManager, times(2)).requestAudioFocus(any());
+ }
+
+ @Test
public void testFocusGainTransient() {
// Focus was lost then regained.
testSnkPlay();
diff --git a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
index 0ca9c2a..c8d421b 100644
--- a/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachineTest.java
@@ -23,12 +23,14 @@
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Looper;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -82,6 +84,9 @@
@Mock
private AvrcpControllerService mAvrcpControllerService;
+ @Mock
+ private Resources mMockResources;
+
AvrcpControllerStateMachine mAvrcpStateMachine;
@Before
@@ -102,7 +107,9 @@
TestUtils.clearAdapterService(mAvrcpAdapterService);
TestUtils.setAdapterService(mA2dpAdapterService);
TestUtils.startService(mA2dpServiceRule, A2dpSinkService.class);
- doReturn(mTargetContext.getResources()).when(mAvrcpControllerService).getResources();
+ when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
+ .thenReturn(true);
+ doReturn(mMockResources).when(mAvrcpControllerService).getResources();
doReturn(15).when(mAudioManager).getStreamMaxVolume(anyInt());
doReturn(8).when(mAudioManager).getStreamVolume(anyInt());
doReturn(true).when(mAudioManager).isVolumeFixed();
@@ -193,6 +200,7 @@
* Test to confirm that a browsing only device can be established (no control)
*/
@Test
+ @FlakyTest
public void testBrowsingOnly() {
Assert.assertEquals(0, mAvrcpControllerService.sBrowseTree.mRootNode.getChildrenCount());
int numBroadcastsSent = setUpConnectedState(false, true);
@@ -343,6 +351,7 @@
* Test media browser fast forward command
*/
@Test
+ @FlakyTest
public void testFastForward() throws Exception {
setUpConnectedState(true, true);
MediaControllerCompat.TransportControls transportControls =
@@ -451,6 +460,7 @@
* Verify that the contents of a player are fetched upon request
*/
@Test
+ @FlakyTest
public void testBrowsingCommands() {
setUpConnectedState(true, true);
final String rootName = "__ROOT__";
@@ -601,6 +611,8 @@
*/
@Test
public void testPlaybackWhileMusicPlaying() {
+ when(mMockResources.getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus))
+ .thenReturn(false);
Assert.assertEquals(AudioManager.AUDIOFOCUS_NONE, A2dpSinkService.getFocusState());
doReturn(true).when(mAudioManager).isMusicActive();
setUpConnectedState(true, true);
@@ -608,7 +620,6 @@
AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
PlaybackStateCompat.STATE_PLAYING);
TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
- verify(mAudioManager, times(1)).isMusicActive();
verify(mAvrcpControllerService,
timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).sendPassThroughCommandNative(
eq(mTestAddress), eq(AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE), eq(KEY_DOWN));
@@ -629,7 +640,6 @@
AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED,
PlaybackStateCompat.STATE_PLAYING);
TestUtils.waitForLooperToFinishScheduledTask(mAvrcpStateMachine.getHandler().getLooper());
- verify(mAudioManager, times(1)).isMusicActive();
TestUtils.waitForLooperToFinishScheduledTask(
A2dpSinkService.getA2dpSinkService().getMainLooper());
Assert.assertEquals(AudioManager.AUDIOFOCUS_GAIN, A2dpSinkService.getFocusState());
diff --git a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index f33d2e0..a65dffb 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -71,6 +71,7 @@
private static final String TAG = AdapterServiceTest.class.getSimpleName();
private AdapterService mAdapterService;
+ private AdapterService.AdapterServiceBinder mServiceBinder;
private @Mock Context mMockContext;
private @Mock ApplicationInfo mMockApplicationInfo;
@@ -119,6 +120,7 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> mAdapterService = new AdapterService());
+ mServiceBinder = new AdapterService.AdapterServiceBinder(mAdapterService);
mMockPackageManager = mock(PackageManager.class);
mMockContentResolver = new MockContentResolver(mMockContext);
MockitoAnnotations.initMocks(this);
@@ -157,7 +159,7 @@
mAdapterService.attach(mMockContext, null, null, null, null, null);
mAdapterService.onCreate();
- mAdapterService.registerCallback(mIBluetoothCallback);
+ mServiceBinder.registerCallback(mIBluetoothCallback);
Config.init(mMockContext);
@@ -167,7 +169,7 @@
@After
public void tearDown() {
- mAdapterService.unregisterCallback(mIBluetoothCallback);
+ mServiceBinder.unregisterCallback(mIBluetoothCallback);
mAdapterService.cleanup();
Config.init(InstrumentationRegistry.getTargetContext());
}
@@ -182,11 +184,11 @@
}
private void doEnable(int invocationNumber, boolean onlyGatt) {
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
final int startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2
- mAdapterService.enable();
+ mAdapterService.enable(false);
verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON,
invocationNumber + 1, CONTEXT_SWITCH_MS);
@@ -200,7 +202,7 @@
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON,
invocationNumber + 1, NATIVE_INIT_MS);
- mAdapterService.onLeServiceUp();
+ mServiceBinder.onLeServiceUp();
verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON,
invocationNumber + 1, CONTEXT_SWITCH_MS);
@@ -220,14 +222,14 @@
verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(2 * invocationNumber + 2))
.sendBroadcast(any(), eq(android.Manifest.permission.BLUETOOTH));
- final int scanMode = mAdapterService.getScanMode();
+ final int scanMode = mServiceBinder.getScanMode();
Assert.assertTrue(scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
|| scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
- Assert.assertTrue(mAdapterService.isEnabled());
+ Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
}
private void doDisable(int invocationNumber, boolean onlyGatt) {
- Assert.assertTrue(mAdapterService.isEnabled());
+ Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
final int startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2
@@ -247,7 +249,7 @@
verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON,
invocationNumber + 1, CONTEXT_SWITCH_MS);
- mAdapterService.onBrEdrDown();
+ mServiceBinder.onBrEdrDown();
verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF,
invocationNumber + 1, CONTEXT_SWITCH_MS);
@@ -260,7 +262,7 @@
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF,
invocationNumber + 1, NATIVE_DISABLE_MS);
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
}
/**
@@ -314,9 +316,9 @@
*/
@Test
public void testGattStartTimeout() {
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
- mAdapterService.enable();
+ mAdapterService.enable(false);
verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, 1,
CONTEXT_SWITCH_MS);
@@ -336,7 +338,7 @@
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
NATIVE_DISABLE_MS);
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
}
/**
@@ -346,7 +348,7 @@
@Test
public void testGattStopTimeout() {
doEnable(0, false);
- Assert.assertTrue(mAdapterService.isEnabled());
+ Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
mAdapterService.disable();
@@ -361,7 +363,7 @@
verifyStateChange(BluetoothAdapter.STATE_TURNING_OFF, BluetoothAdapter.STATE_BLE_ON, 1,
CONTEXT_SWITCH_MS);
- mAdapterService.onBrEdrDown();
+ mServiceBinder.onBrEdrDown();
verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_BLE_TURNING_OFF, 1,
CONTEXT_SWITCH_MS);
@@ -372,7 +374,7 @@
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
AdapterState.BLE_STOP_TIMEOUT_DELAY + NATIVE_DISABLE_MS);
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
}
/**
@@ -381,9 +383,9 @@
*/
@Test
public void testProfileStartTimeout() {
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
- mAdapterService.enable();
+ mAdapterService.enable(false);
verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON, 1,
CONTEXT_SWITCH_MS);
@@ -396,7 +398,7 @@
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON, 1,
NATIVE_INIT_MS);
- mAdapterService.onLeServiceUp();
+ mServiceBinder.onLeServiceUp();
verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON, 1,
CONTEXT_SWITCH_MS);
@@ -426,7 +428,7 @@
public void testProfileStopTimeout() {
doEnable(0, false);
- Assert.assertTrue(mAdapterService.isEnabled());
+ Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
mAdapterService.disable();
@@ -448,7 +450,7 @@
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
AdapterState.BLE_STOP_TIMEOUT_DELAY + NATIVE_DISABLE_MS);
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
}
/**
@@ -462,7 +464,7 @@
SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, "false");
doEnable(0, false);
- Assert.assertTrue(mAdapterService.isEnabled());
+ Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
Assert.assertFalse(
SystemProperties.get(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY,
@@ -495,7 +497,7 @@
verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF, BluetoothAdapter.STATE_OFF, 1,
NATIVE_DISABLE_MS);
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
// Restore earlier setting
SystemProperties.set(AdapterService.BLUETOOTH_BTSNOOP_LOG_MODE_PROPERTY, snoopSetting);
@@ -518,7 +520,7 @@
*/
@Test
public void testObfuscateBluetoothAddress_BluetoothDisabled() {
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
Assert.assertNotNull(metricsSalt);
BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
@@ -534,9 +536,9 @@
*/
@Test
public void testObfuscateBluetoothAddress_BluetoothEnabled() {
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
doEnable(0, false);
- Assert.assertTrue(mAdapterService.isEnabled());
+ Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
Assert.assertNotNull(metricsSalt);
BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
@@ -551,7 +553,7 @@
*/
@Test
public void testObfuscateBluetoothAddress_PersistentBetweenToggle() {
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
Assert.assertNotNull(metricsSalt);
BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
@@ -562,7 +564,7 @@
obfuscatedAddress1);
// Enable
doEnable(0, false);
- Assert.assertTrue(mAdapterService.isEnabled());
+ Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
Assert.assertTrue(obfuscatedAddress3.length > 0);
Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
@@ -570,7 +572,7 @@
obfuscatedAddress1);
// Disable
doDisable(0, false);
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
Assert.assertTrue(obfuscatedAddress4.length > 0);
Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
@@ -587,7 +589,7 @@
PackageManager.NameNotFoundException {
byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
Assert.assertNotNull(metricsSalt);
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
Assert.assertTrue(obfuscatedAddress1.length > 0);
@@ -596,7 +598,7 @@
obfuscatedAddress1);
tearDown();
setUp();
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
Assert.assertTrue(obfuscatedAddress2.length > 0);
Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
@@ -623,12 +625,12 @@
+ " after factory reset")
@Test
public void testObfuscateBluetoothAddress_FactoryReset() {
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
Assert.assertTrue(obfuscatedAddress1.length > 0);
Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
- mAdapterService.factoryReset();
+ mServiceBinder.factoryReset();
byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
Assert.assertTrue(obfuscatedAddress2.length > 0);
Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
@@ -640,7 +642,7 @@
Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
Assert.assertArrayEquals(obfuscatedAddress3,
obfuscatedAddress2);
- mAdapterService.factoryReset();
+ mServiceBinder.factoryReset();
byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
Assert.assertTrue(obfuscatedAddress4.length > 0);
Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
@@ -657,14 +659,14 @@
PackageManager.NameNotFoundException {
byte[] metricsSalt1 = getMetricsSalt(mAdapterConfig);
Assert.assertNotNull(metricsSalt1);
- Assert.assertFalse(mAdapterService.isEnabled());
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
Assert.assertTrue(obfuscatedAddress1.length > 0);
Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
Assert.assertArrayEquals(obfuscateInJava(metricsSalt1, device),
obfuscatedAddress1);
- mAdapterService.factoryReset();
+ mServiceBinder.factoryReset();
tearDown();
setUp();
// Cannot verify metrics salt since it is not written to disk until native cleanup
diff --git a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
index dafdc5a..16f2712 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java
@@ -33,6 +33,7 @@
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.hfp.HeadsetService;
import org.junit.After;
@@ -44,6 +45,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -62,6 +64,7 @@
@Mock private ServiceFactory mServiceFactory;
@Mock private HeadsetService mHeadsetService;
@Mock private A2dpService mA2dpService;
+ @Mock private DatabaseManager mDatabaseManager;
@Before
public void setUp() throws Exception {
@@ -103,26 +106,28 @@
@Test
public void testProcessInitProfilePriorities() {
BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
- // Mock the HeadsetService to return undefined priority
- when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ // Mock the HeadsetService to return unknown connection policy
+ when(mHeadsetService.getConnectionPolicy(device))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
- // Mock the A2DP service to return undefined priority
- when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ // Mock the A2DP service to return undefined unknown connection policy
+ when(mA2dpService.getConnectionPolicy(device))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
// Inject an event for UUIDs updated for a remote device with only HFP enabled
Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
ParcelUuid[] uuids = new ParcelUuid[2];
- uuids[0] = BluetoothUuid.Handsfree;
- uuids[1] = BluetoothUuid.AudioSink;
+ uuids[0] = BluetoothUuid.HFP;
+ uuids[1] = BluetoothUuid.A2DP_SINK;
intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids);
mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
// Check that the priorities of the devices for preferred profiles are set to ON
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(device),
- eq(BluetoothProfile.PRIORITY_ON));
- verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(eq(device),
- eq(BluetoothProfile.PRIORITY_ON));
+ verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setConnectionPolicy(eq(device),
+ eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
+ verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setConnectionPolicy(eq(device),
+ eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
}
/**
@@ -137,16 +142,17 @@
when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
when(mAdapterService.isQuietModeEnabled()).thenReturn(false);
- // Return a list of bonded devices (just one)
- BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
- bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
- when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
+ // Return a list of connection order
+ BluetoothDevice bondedDevice = TestUtils.getTestDevice(mAdapter, 0);
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(bondedDevice);
+ when(mAdapterService.getBondState(bondedDevice)).thenReturn(BluetoothDevice.BOND_BONDED);
- // Return PRIORITY_AUTO_CONNECT over HFP and A2DP
- when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ // Return CONNECTION_POLICY_ALLOWED over HFP and A2DP
+ when(mHeadsetService.getConnectionPolicy(bondedDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(bondedDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// Inject an event that the adapter is turned on.
Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
@@ -154,13 +160,12 @@
mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
// Check that we got a request to connect over HFP and A2DP
- verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevices[0]));
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevices[0]));
+ verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevice));
+ verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connect(eq(bondedDevice));
}
/**
- * Test that when an auto connect device is disconnected, its priority is set to ON if its
- * original priority is auto connect
+ * Test that when an active device is disconnected, we will not auto connect it
*/
@Test
public void testDisconnectNoAutoConnect() {
@@ -168,93 +173,85 @@
when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
when(mAdapterService.isQuietModeEnabled()).thenReturn(false);
- // Return a list of bonded devices (just one)
- BluetoothDevice[] bondedDevices = new BluetoothDevice[4];
- bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
- bondedDevices[1] = TestUtils.getTestDevice(mAdapter, 1);
- bondedDevices[2] = TestUtils.getTestDevice(mAdapter, 2);
- bondedDevices[3] = TestUtils.getTestDevice(mAdapter, 3);
- when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
+ // Return a list of connection order
+ List<BluetoothDevice> connectionOrder = new ArrayList<>();
+ connectionOrder.add(TestUtils.getTestDevice(mAdapter, 0));
+ connectionOrder.add(TestUtils.getTestDevice(mAdapter, 1));
+ connectionOrder.add(TestUtils.getTestDevice(mAdapter, 2));
+ connectionOrder.add(TestUtils.getTestDevice(mAdapter, 3));
+
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(
+ connectionOrder.get(0));
// Make all devices auto connect
- when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mHeadsetService.getPriority(bondedDevices[3])).thenReturn(
- BluetoothProfile.PRIORITY_OFF);
+ when(mHeadsetService.getConnectionPolicy(connectionOrder.get(0))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mHeadsetService.getConnectionPolicy(connectionOrder.get(1))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mHeadsetService.getConnectionPolicy(connectionOrder.get(2))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mHeadsetService.getConnectionPolicy(connectionOrder.get(3))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Make one of the device active
Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[0]);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(0));
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
- // All other disconnected device's priority is set to ON, except disabled ones
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0],
- BluetoothProfile.PRIORITY_ON);
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1],
- BluetoothProfile.PRIORITY_ON);
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[2],
- BluetoothProfile.PRIORITY_ON);
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[0],
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt());
- when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
- BluetoothProfile.PRIORITY_ON);
- when(mHeadsetService.getPriority(bondedDevices[2])).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ // Only calls setConnection on device connectionOrder.get(0) with STATE_CONNECTED
+ verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setConnection(
+ connectionOrder.get(0), true);
+ verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(1)), anyBoolean());
+ verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(2)), anyBoolean());
+ verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(3)), anyBoolean());
// Make another device active
- when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
+ when(mHeadsetService.getConnectionState(connectionOrder.get(1))).thenReturn(
BluetoothProfile.STATE_CONNECTED);
intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bondedDevices[1]);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(1));
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
- // This device should be set to auto connect while the first device is reset to ON
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
- bondedDevices[0], BluetoothProfile.PRIORITY_ON);
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setPriority(bondedDevices[1],
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- verify(mHeadsetService, never()).setPriority(eq(bondedDevices[3]), anyInt());
- when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_ON);
- when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ // Only calls setConnection on device connectionOrder.get(1) with STATE_CONNECTED
+ verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection(
+ connectionOrder.get(0), true);
+ verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection(
+ connectionOrder.get(1), true);
+ verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(2)), anyBoolean());
+ verify(mDatabaseManager, never()).setConnection(eq(connectionOrder.get(3)), anyBoolean());
- // Set active device to null
- when(mHeadsetService.getConnectionState(bondedDevices[1])).thenReturn(
+ // Disconnect a2dp for the device
+ when(mHeadsetService.getConnectionState(connectionOrder.get(1))).thenReturn(
BluetoothProfile.STATE_DISCONNECTED);
- intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) null);
+ intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, connectionOrder.get(1));
+ intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTED);
+ intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
- // Verify that the priority of previous active device won't be changed while active device
- // set to null
- verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setPriority(
- bondedDevices[1], BluetoothProfile.PRIORITY_ON);
- verify(mHeadsetService).setPriority(bondedDevices[1],
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- verify(mHeadsetService, never()).setPriority(bondedDevices[1],
- BluetoothProfile.PRIORITY_OFF);
+ // Verify that we do not call setConnection, but instead setDisconnection on disconnect
+ verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setConnection(
+ connectionOrder.get(1), true);
+ verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setDisconnection(
+ connectionOrder.get(1));
// Make the current active device fail to connect
- when(mA2dpService.getConnectionState(bondedDevices[1])).thenReturn(
+ when(mA2dpService.getConnectionState(connectionOrder.get(1))).thenReturn(
BluetoothProfile.STATE_DISCONNECTED);
- updateProfileConnectionStateHelper(bondedDevices[1], BluetoothProfile.HEADSET,
+ updateProfileConnectionStateHelper(connectionOrder.get(1), BluetoothProfile.HEADSET,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
- // This device should be set to ON
- verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).setPriority(
- bondedDevices[1], BluetoothProfile.PRIORITY_ON);
+ // Verify we don't call deleteConnection as that only happens when we disconnect a2dp
+ verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1)).setDisconnection(
+ connectionOrder.get(1));
- // Verify that we are not setting priorities to random devices and values
- verify(mHeadsetService, times(7)).setPriority(any(BluetoothDevice.class), anyInt());
+ // Verify we didn't have any unexpected calls to setConnection or deleteConnection
+ verify(mDatabaseManager, times(2)).setConnection(any(BluetoothDevice.class), anyBoolean());
+ verify(mDatabaseManager, times(1)).setDisconnection(any(BluetoothDevice.class));
}
/**
@@ -270,10 +267,10 @@
// Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
// auto-connectable.
- when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ when(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
@@ -304,17 +301,18 @@
*/
@Test
public void testReconnectOnPartialConnect_PreviousPartialFail() {
- // Return a list of bonded devices (just one)
- BluetoothDevice[] bondedDevices = new BluetoothDevice[1];
- bondedDevices[0] = TestUtils.getTestDevice(mAdapter, 0);
- when(mAdapterService.getBondedDevices()).thenReturn(bondedDevices);
+ List<BluetoothDevice> connectionOrder = new ArrayList<>();
+ connectionOrder.add(TestUtils.getTestDevice(mAdapter, 0));
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager.getMostRecentlyConnectedA2dpDevice()).thenReturn(
+ connectionOrder.get(0));
// Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
// auto-connectable.
- when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ when(mHeadsetService.getConnectionPolicy(connectionOrder.get(0))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(connectionOrder.get(0))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
@@ -322,54 +320,54 @@
// To enable that we need to make sure that HeadsetService returns the device among a list
// of connected devices
ArrayList<BluetoothDevice> hsConnectedDevices = new ArrayList<>();
- hsConnectedDevices.add(bondedDevices[0]);
+ hsConnectedDevices.add(connectionOrder.get(0));
when(mHeadsetService.getConnectedDevices()).thenReturn(hsConnectedDevices);
// Also the A2DP should say that its not connected for same device
- when(mA2dpService.getConnectionState(bondedDevices[0])).thenReturn(
+ when(mA2dpService.getConnectionState(connectionOrder.get(0))).thenReturn(
BluetoothProfile.STATE_DISCONNECTED);
// We send a connection success event for one profile since the re-connect *only* works if
// we have already connected successfully over one of the profiles
- updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+ updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
// Check that we get a call to A2DP reconnect
verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
- bondedDevices[0]);
+ connectionOrder.get(0));
// We send a connection failure event for the attempted profile, and keep the connected
// profile connected.
- updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.A2DP,
+ updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.A2DP,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
// Verify no one changes the priority of the failed profile
- verify(mA2dpService, never()).setPriority(eq(bondedDevices[0]), anyInt());
+ verify(mA2dpService, never()).setConnectionPolicy(eq(connectionOrder.get(0)), anyInt());
// Send a connection success event for one profile again without disconnecting all profiles
- updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+ updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
// Check that we won't get a call to A2DP reconnect again before all profiles disconnected
verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS)).connect(
- bondedDevices[0]);
+ connectionOrder.get(0));
// Send a disconnection event for all connected profiles
- hsConnectedDevices.remove(bondedDevices[0]);
- updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+ hsConnectedDevices.remove(connectionOrder.get(0));
+ updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
// Send a connection success event for one profile again to trigger re-connect
- hsConnectedDevices.add(bondedDevices[0]);
- updateProfileConnectionStateHelper(bondedDevices[0], BluetoothProfile.HEADSET,
+ hsConnectedDevices.add(connectionOrder.get(0));
+ updateProfileConnectionStateHelper(connectionOrder.get(0), BluetoothProfile.HEADSET,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
// Check that we get a call to A2DP connect again
verify(mA2dpService, timeout(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).times(2)).connect(
- bondedDevices[0]);
+ connectionOrder.get(0));
}
/**
@@ -394,10 +392,10 @@
// Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles
// are auto-connectable.
- when(mHeadsetService.getPriority(testDevice)).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(testDevice)).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(testDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// We want to trigger (in CONNECT_OTHER_PROFILES_TIMEOUT) a call to connect A2DP
// To enable that we need to make sure that HeadsetService returns the device as list
// of connected devices.
@@ -438,15 +436,15 @@
}
/**
- * Test that the connect priority of all devices are set as appropriate if there is one
+ * Test that the connection policy of all devices are set as appropriate if there is one
* connected device.
* - The HFP and A2DP connect priority for connected devices is set to
* BluetoothProfile.PRIORITY_AUTO_CONNECT
* - The HFP and A2DP connect priority for bonded devices is set to
- * BluetoothProfile.PRIORITY_ON
+ * BluetoothProfile.CONNECTION_POLICY_ALLOWED
*/
@Test
- public void testSetPriorityMultipleDevices() {
+ public void testSetConnectionPolicyMultipleDevices() {
// testDevices[0] - connected for both HFP and A2DP
// testDevices[1] - connected only for HFP - will auto-connect for A2DP
// testDevices[2] - connected only for A2DP - will auto-connect for HFP
@@ -465,28 +463,31 @@
if (i == 0) {
hsConnectedDevices.add(testDevice);
a2dpConnectedDevices.add(testDevice);
- when(mHeadsetService.getPriority(testDevice)).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(testDevice)).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(testDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
if (i == 1) {
hsConnectedDevices.add(testDevice);
- when(mHeadsetService.getPriority(testDevice)).thenReturn(
- BluetoothProfile.PRIORITY_ON);
- when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(testDevice))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
if (i == 2) {
a2dpConnectedDevices.add(testDevice);
- when(mHeadsetService.getPriority(testDevice)).thenReturn(
- BluetoothProfile.PRIORITY_ON);
- when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(testDevice))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
if (i == 3) {
// Device not connected
- when(mHeadsetService.getPriority(testDevice)).thenReturn(
- BluetoothProfile.PRIORITY_ON);
- when(mA2dpService.getPriority(testDevice)).thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetService.getConnectionPolicy(testDevice)).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(testDevice))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
}
when(mAdapterService.getBondedDevices()).thenReturn(testDevices);
@@ -532,21 +533,22 @@
BluetoothProfile.STATE_CONNECTED);
// Check the connect priorities for all devices
- // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
+ // - testDevices[0] - connected for HFP and A2DP: setConnectionPolicy() should not be called
// - testDevices[1] - connection state changed for HFP should no longer trigger auto
// connect priority change since it is now triggered by A2DP active
// device change intent
- // - testDevices[2] - connected for A2DP: setPriority() should not be called
- // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called
- verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt());
- verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt());
- verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]),
+ // - testDevices[2] - connected for A2DP: setConnectionPolicy() should not be called
+ // - testDevices[3] - not connected for HFP nor A2DP: setConnectionPolicy() should not be
+ // called
+ verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt());
+ verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt());
+ verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[1]),
eq(BluetoothProfile.PRIORITY_AUTO_CONNECT));
- verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt());
- verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt());
- verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]), anyInt());
- verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt());
- verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt());
+ verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt());
+ verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt());
+ verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt());
+ verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt());
+ verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt());
clearInvocations(mHeadsetService, mA2dpService);
// Generate connection state changed for A2DP for testDevices[2] and trigger
@@ -565,21 +567,22 @@
BluetoothProfile.STATE_CONNECTED);
// Check the connect priorities for all devices
- // - testDevices[0] - connected for HFP and A2DP: setPriority() should not be called
- // - testDevices[1] - connected for HFP and A2DP: setPriority() should not be called
+ // - testDevices[0] - connected for HFP and A2DP: setConnectionPolicy() should not be called
+ // - testDevices[1] - connected for HFP and A2DP: setConnectionPolicy() should not be called
// - testDevices[2] - connection state changed for A2DP should no longer trigger auto
// connect priority change since it is now triggered by A2DP
// active device change intent
- // - testDevices[3] - not connected for HFP nor A2DP: setPriority() should not be called
- verify(mHeadsetService, times(0)).setPriority(eq(testDevices[0]), anyInt());
- verify(mA2dpService, times(0)).setPriority(eq(testDevices[0]), anyInt());
- verify(mHeadsetService, times(0)).setPriority(eq(testDevices[1]), anyInt());
- verify(mA2dpService, times(0)).setPriority(eq(testDevices[1]), anyInt());
- verify(mHeadsetService, times(0)).setPriority(eq(testDevices[2]), anyInt());
- verify(mA2dpService, times(0)).setPriority(eq(testDevices[2]),
+ // - testDevices[3] - not connected for HFP nor A2DP: setConnectionPolicy() should not be
+ // called
+ verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt());
+ verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[0]), anyInt());
+ verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt());
+ verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[1]), anyInt());
+ verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[2]), anyInt());
+ verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[2]),
eq(BluetoothProfile.PRIORITY_AUTO_CONNECT));
- verify(mHeadsetService, times(0)).setPriority(eq(testDevices[3]), anyInt());
- verify(mA2dpService, times(0)).setPriority(eq(testDevices[3]), anyInt());
+ verify(mHeadsetService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt());
+ verify(mA2dpService, times(0)).setConnectionPolicy(eq(testDevices[3]), anyInt());
clearInvocations(mHeadsetService, mA2dpService);
}
@@ -595,10 +598,10 @@
// Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
// auto-connectable.
- when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ when(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
@@ -641,14 +644,14 @@
// Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
// auto-connectable.
- when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(bondedDevices[1])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ when(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mHeadsetService.getConnectionPolicy(bondedDevices[1])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(bondedDevices[1])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
@@ -696,14 +699,14 @@
// Return PRIORITY_AUTO_CONNECT over HFP and A2DP. This would imply that the profiles are
// auto-connectable.
- when(mHeadsetService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(bondedDevices[0])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mHeadsetService.getPriority(bondedDevices[1])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
- when(mA2dpService.getPriority(bondedDevices[1])).thenReturn(
- BluetoothProfile.PRIORITY_AUTO_CONNECT);
+ when(mHeadsetService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(bondedDevices[0])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mHeadsetService.getConnectionPolicy(bondedDevices[1])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mA2dpService.getConnectionPolicy(bondedDevices[1])).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
@@ -738,10 +741,12 @@
public void testNoSupportedUuids() {
// Mock the HeadsetService to return undefined priority
BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
- when(mHeadsetService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ when(mHeadsetService.getConnectionPolicy(device))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
// Mock the A2DP service to return undefined priority
- when(mA2dpService.getPriority(device)).thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ when(mA2dpService.getConnectionPolicy(device))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
// Inject an event for UUIDs updated for a remote device with only HFP enabled
Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
@@ -752,9 +757,10 @@
// Check that we do not crash and not call any setPriority methods
verify(mHeadsetService,
- after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never()).setPriority(eq(device),
- eq(BluetoothProfile.PRIORITY_ON));
- verify(mA2dpService, never()).setPriority(eq(device), eq(BluetoothProfile.PRIORITY_ON));
+ after(CONNECT_OTHER_PROFILES_TIMEOUT_WAIT_MILLIS).never())
+ .setConnectionPolicy(eq(device), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
+ verify(mA2dpService, never())
+ .setConnectionPolicy(eq(device), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
}
private void updateProfileConnectionStateHelper(BluetoothDevice device, int profileId,
diff --git a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index f1d9d16..81abfc6 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -270,8 +270,6 @@
when(mAdapterService.getState()).thenReturn(BluetoothAdapter.STATE_ON);
mRemoteDevices.aclStateChangeCallback(0, Utils.getByteAddress(mDevice1),
AbstractionLayer.BT_ACL_STATE_DISCONNECTED);
- verify(mAdapterService).getState();
- verify(mAdapterService).getConnectionState(mDevice1);
// Verify ACTION_ACL_DISCONNECTED and BATTERY_LEVEL_CHANGED intent are sent
verify(mAdapterService, times(3)).sendBroadcast(mIntentArgument.capture(),
mStringArgument.capture());
@@ -294,8 +292,6 @@
mStringArgument.capture());
verifyBatteryLevelChangedIntent(mDevice1, batteryLevel, mIntentArgument);
Assert.assertEquals(AdapterService.BLUETOOTH_PERM, mStringArgument.getValue());
-
- verifyNoMoreInteractions(mAdapterService);
}
@Test
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
index 2cb3630..0c71659 100644
--- a/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -16,7 +16,15 @@
package com.android.bluetooth.btservice.storage;
-import static org.mockito.Mockito.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
@@ -60,9 +68,12 @@
private DatabaseManager mDatabaseManager;
private BluetoothDevice mTestDevice;
private BluetoothDevice mTestDevice2;
+ private BluetoothDevice mTestDevice3;
private static final String LOCAL_STORAGE = "LocalStorage";
private static final String TEST_BT_ADDR = "11:22:33:44:55:66";
+ private static final String TEST_BT_ADDR2 = "66:55:44:33:22:11";
+ private static final String TEST_BT_ADDR3 = "12:34:56:65:43:21";
private static final String OTHER_BT_ADDR1 = "11:11:11:11:11:11";
private static final String OTHER_BT_ADDR2 = "22:22:22:22:22:22";
private static final String DB_NAME = "test_db";
@@ -82,6 +93,8 @@
TestUtils.setAdapterService(mAdapterService);
mTestDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR);
+ mTestDevice2 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR2);
+ mTestDevice3 = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(TEST_BT_ADDR3);
// Create a memory database for DatabaseManager instead of use a real database.
mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
@@ -113,8 +126,8 @@
restartDatabaseManagerHelper();
for (int id = 0; id < BluetoothProfile.MAX_PROFILE_ID; id++) {
- Assert.assertEquals(BluetoothProfile.PRIORITY_UNDEFINED,
- mDatabaseManager.getProfilePriority(mTestDevice, id));
+ Assert.assertEquals(BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+ mDatabaseManager.getProfileConnectionPolicy(mTestDevice, id));
}
Assert.assertEquals(BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN,
@@ -126,35 +139,36 @@
for (int id = 0; id < MAX_META_ID; id++) {
Assert.assertNull(mDatabaseManager.getCustomMeta(mTestDevice, id));
}
+
+ mDatabaseManager.factoryReset();
+ mDatabaseManager.mMetadataCache.clear();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
}
@Test
- public void testSetGetProfilePriority() {
- int badPriority = -100;
+ public void testSetGetProfileConnectionPolicy() {
+ int badConnectionPolicy = -100;
// Cases of device not in database
- testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_UNDEFINED,
- BluetoothProfile.PRIORITY_UNDEFINED, true);
- testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_OFF,
- BluetoothProfile.PRIORITY_OFF, true);
- testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_ON,
- BluetoothProfile.PRIORITY_ON, true);
- testSetGetProfilePriorityCase(false, BluetoothProfile.PRIORITY_AUTO_CONNECT,
- BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
- testSetGetProfilePriorityCase(false, badPriority,
- BluetoothProfile.PRIORITY_UNDEFINED, false);
+ testSetGetProfileConnectionPolicyCase(false, BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
+ testSetGetProfileConnectionPolicyCase(false, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, true);
+ testSetGetProfileConnectionPolicyCase(false, BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
+ testSetGetProfileConnectionPolicyCase(false, badConnectionPolicy,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
// Cases of device already in database
- testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_UNDEFINED,
- BluetoothProfile.PRIORITY_UNDEFINED, true);
- testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_OFF,
- BluetoothProfile.PRIORITY_OFF, true);
- testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_ON,
- BluetoothProfile.PRIORITY_ON, true);
- testSetGetProfilePriorityCase(true, BluetoothProfile.PRIORITY_AUTO_CONNECT,
- BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
- testSetGetProfilePriorityCase(true, badPriority,
- BluetoothProfile.PRIORITY_UNDEFINED, false);
+ testSetGetProfileConnectionPolicyCase(true, BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
+ testSetGetProfileConnectionPolicyCase(true, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, true);
+ testSetGetProfileConnectionPolicyCase(true, BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
+ testSetGetProfileConnectionPolicyCase(true, badConnectionPolicy,
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
}
@Test
@@ -249,10 +263,10 @@
Metadata checkData = list.get(0);
Assert.assertEquals(TEST_BT_ADDR, checkData.getAddress());
- mDatabase.deleteAll();
+ mDatabaseManager.factoryReset();
+ mDatabaseManager.mMetadataCache.clear();
// Wait for clear database
TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
- mDatabaseManager.mMetadataCache.clear();
}
@Test
@@ -299,14 +313,14 @@
// Check whether the devices are in the database
Metadata checkData1 = list.get(0);
- Assert.assertEquals(OTHER_BT_ADDR1, checkData1.getAddress());
+ Assert.assertEquals(OTHER_BT_ADDR2, checkData1.getAddress());
Metadata checkData2 = list.get(1);
- Assert.assertEquals(OTHER_BT_ADDR2, checkData2.getAddress());
+ Assert.assertEquals(OTHER_BT_ADDR1, checkData2.getAddress());
- mDatabase.deleteAll();
+ mDatabaseManager.factoryReset();
+ mDatabaseManager.mMetadataCache.clear();
// Wait for clear database
TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
- mDatabaseManager.mMetadataCache.clear();
}
@@ -390,6 +404,176 @@
}
@Test
+ public void testSetConnection() {
+ // Verify pre-conditions to ensure a fresh test
+ Assert.assertEquals(0, mDatabaseManager.mMetadataCache.size());
+ Assert.assertNotNull(mTestDevice);
+ Assert.assertNotNull(mTestDevice2);
+ Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+
+ // Set the first device's connection
+ mDatabaseManager.setConnection(mTestDevice, true);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertTrue(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ List<BluetoothDevice> mostRecentlyConnectedDevicesOrdered =
+ mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ Assert.assertEquals(1, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
+
+ // Setting the second device's connection
+ mDatabaseManager.setConnection(mTestDevice2, true);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertTrue(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertEquals(mTestDevice2, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+
+ // Connect first test device again
+ mDatabaseManager.setConnection(mTestDevice, true);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertTrue(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(1));
+
+ // Disconnect first test device's connection
+ mDatabaseManager.setDisconnection(mTestDevice);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(2, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(1));
+
+ // Connect third test device (non-a2dp device)
+ mDatabaseManager.setConnection(mTestDevice3, false);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+ Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+ // Connect first test device again
+ mDatabaseManager.setConnection(mTestDevice, true);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertTrue(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+ Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(1));
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+ // Connect third test device again and ensure it doesn't reset active a2dp device
+ mDatabaseManager.setConnection(mTestDevice3, false);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertTrue(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+ Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+ // Disconnect second test device
+ mDatabaseManager.setDisconnection(mTestDevice2);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertTrue(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+ Assert.assertEquals(mTestDevice, mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+ // Disconnect first test device
+ mDatabaseManager.setDisconnection(mTestDevice);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+ Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+ // Disconnect third test device
+ mDatabaseManager.setDisconnection(mTestDevice3);
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice2.getAddress()).is_active_a2dp_device);
+ Assert.assertFalse(mDatabaseManager
+ .mMetadataCache.get(mTestDevice3.getAddress()).is_active_a2dp_device);
+ Assert.assertNull(mDatabaseManager.getMostRecentlyConnectedA2dpDevice());
+ mostRecentlyConnectedDevicesOrdered = mDatabaseManager.getMostRecentlyConnectedDevices();
+ Assert.assertEquals(3, mostRecentlyConnectedDevicesOrdered.size());
+ Assert.assertEquals(mTestDevice3, mostRecentlyConnectedDevicesOrdered.get(0));
+ Assert.assertEquals(mTestDevice, mostRecentlyConnectedDevicesOrdered.get(1));
+ Assert.assertEquals(mTestDevice2, mostRecentlyConnectedDevicesOrdered.get(2));
+
+ mDatabaseManager.factoryReset();
+ mDatabaseManager.mMetadataCache.clear();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ }
+
+ @Test
public void testDatabaseMigration_100_101() throws IOException {
// Create a database with version 100
SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 100);
@@ -450,7 +634,7 @@
device.put("unthethered_left_charging", testString);
device.put("unthethered_right_charging", testString);
device.put("unthethered_case_charging", testString);
- Assert.assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+ assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
CoreMatchers.not(-1));
// Check the metadata names on version 101
@@ -533,22 +717,234 @@
}
}
+ @Test
+ public void testDatabaseMigration_102_103() throws IOException {
+ String testString = "TEST STRING";
+
+ // Create a database with version 102
+ SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 102);
+ Cursor cursor = db.query("SELECT * FROM metadata");
+
+ // insert a device to the database
+ ContentValues device = new ContentValues();
+ device.put("address", TEST_BT_ADDR);
+ device.put("migrated", false);
+ device.put("a2dpSupportsOptionalCodecs", -1);
+ device.put("a2dpOptionalCodecsEnabled", -1);
+ device.put("a2dp_priority", 1000);
+ device.put("a2dp_sink_priority", 1000);
+ device.put("hfp_priority", 1000);
+ device.put("hfp_client_priority", 1000);
+ device.put("hid_host_priority", 1000);
+ device.put("pan_priority", 1000);
+ device.put("pbap_priority", 1000);
+ device.put("pbap_client_priority", 1000);
+ device.put("map_priority", 1000);
+ device.put("sap_priority", 1000);
+ device.put("hearing_aid_priority", 1000);
+ device.put("map_client_priority", 1000);
+ device.put("manufacturer_name", testString);
+ device.put("model_name", testString);
+ device.put("software_version", testString);
+ device.put("hardware_version", testString);
+ device.put("companion_app", testString);
+ device.put("main_icon", testString);
+ device.put("is_untethered_headset", testString);
+ device.put("untethered_left_icon", testString);
+ device.put("untethered_right_icon", testString);
+ device.put("untethered_case_icon", testString);
+ device.put("untethered_left_battery", testString);
+ device.put("untethered_right_battery", testString);
+ device.put("untethered_case_battery", testString);
+ device.put("untethered_left_charging", testString);
+ device.put("untethered_right_charging", testString);
+ device.put("untethered_case_charging", testString);
+ assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+ CoreMatchers.not(-1));
+
+ // Check the metadata names on version 102
+ assertHasColumn(cursor, "a2dp_priority", true);
+ assertHasColumn(cursor, "a2dp_sink_priority", true);
+ assertHasColumn(cursor, "hfp_priority", true);
+ assertHasColumn(cursor, "hfp_client_priority", true);
+ assertHasColumn(cursor, "hid_host_priority", true);
+ assertHasColumn(cursor, "pan_priority", true);
+ assertHasColumn(cursor, "pbap_priority", true);
+ assertHasColumn(cursor, "pbap_client_priority", true);
+ assertHasColumn(cursor, "map_priority", true);
+ assertHasColumn(cursor, "sap_priority", true);
+ assertHasColumn(cursor, "hearing_aid_priority", true);
+ assertHasColumn(cursor, "map_client_priority", true);
+
+ // Migrate database from 102 to 103
+ db.close();
+ db = testHelper.runMigrationsAndValidate(DB_NAME, 103, true,
+ MetadataDatabase.MIGRATION_102_103);
+ cursor = db.query("SELECT * FROM metadata");
+
+ // metadata names should be changed on version 103
+ assertHasColumn(cursor, "a2dp_priority", false);
+ assertHasColumn(cursor, "a2dp_sink_priority", false);
+ assertHasColumn(cursor, "hfp_priority", false);
+ assertHasColumn(cursor, "hfp_client_priority", false);
+ assertHasColumn(cursor, "hid_host_priority", false);
+ assertHasColumn(cursor, "pan_priority", false);
+ assertHasColumn(cursor, "pbap_priority", false);
+ assertHasColumn(cursor, "pbap_client_priority", false);
+ assertHasColumn(cursor, "map_priority", false);
+ assertHasColumn(cursor, "sap_priority", false);
+ assertHasColumn(cursor, "hearing_aid_priority", false);
+ assertHasColumn(cursor, "map_client_priority", false);
+
+ assertHasColumn(cursor, "a2dp_connection_policy", true);
+ assertHasColumn(cursor, "a2dp_sink_connection_policy", true);
+ assertHasColumn(cursor, "hfp_connection_policy", true);
+ assertHasColumn(cursor, "hfp_client_connection_policy", true);
+ assertHasColumn(cursor, "hid_host_connection_policy", true);
+ assertHasColumn(cursor, "pan_connection_policy", true);
+ assertHasColumn(cursor, "pbap_connection_policy", true);
+ assertHasColumn(cursor, "pbap_client_connection_policy", true);
+ assertHasColumn(cursor, "map_connection_policy", true);
+ assertHasColumn(cursor, "sap_connection_policy", true);
+ assertHasColumn(cursor, "hearing_aid_connection_policy", true);
+ assertHasColumn(cursor, "map_client_connection_policy", true);
+
+ while (cursor.moveToNext()) {
+ // Check PRIORITY_AUTO_CONNECT (1000) was replaced with CONNECTION_POLICY_ALLOWED (100)
+ assertColumnIntData(cursor, "a2dp_connection_policy", 100);
+ assertColumnIntData(cursor, "a2dp_sink_connection_policy", 100);
+ assertColumnIntData(cursor, "hfp_connection_policy", 100);
+ assertColumnIntData(cursor, "hfp_client_connection_policy", 100);
+ assertColumnIntData(cursor, "hid_host_connection_policy", 100);
+ assertColumnIntData(cursor, "pan_connection_policy", 100);
+ assertColumnIntData(cursor, "pbap_connection_policy", 100);
+ assertColumnIntData(cursor, "pbap_client_connection_policy", 100);
+ assertColumnIntData(cursor, "map_connection_policy", 100);
+ assertColumnIntData(cursor, "sap_connection_policy", 100);
+ assertColumnIntData(cursor, "hearing_aid_connection_policy", 100);
+ assertColumnIntData(cursor, "map_client_connection_policy", 100);
+
+ // Check whether metadata data type are blob
+ assertColumnBlob(cursor, "manufacturer_name");
+ assertColumnBlob(cursor, "model_name");
+ assertColumnBlob(cursor, "software_version");
+ assertColumnBlob(cursor, "hardware_version");
+ assertColumnBlob(cursor, "companion_app");
+ assertColumnBlob(cursor, "main_icon");
+ assertColumnBlob(cursor, "is_untethered_headset");
+ assertColumnBlob(cursor, "untethered_left_icon");
+ assertColumnBlob(cursor, "untethered_right_icon");
+ assertColumnBlob(cursor, "untethered_case_icon");
+ assertColumnBlob(cursor, "untethered_left_battery");
+ assertColumnBlob(cursor, "untethered_right_battery");
+ assertColumnBlob(cursor, "untethered_case_battery");
+ assertColumnBlob(cursor, "untethered_left_charging");
+ assertColumnBlob(cursor, "untethered_right_charging");
+ assertColumnBlob(cursor, "untethered_case_charging");
+
+ // Check whether metadata values are migrated to version 103 successfully
+ assertColumnBlobData(cursor, "manufacturer_name", testString.getBytes());
+ assertColumnBlobData(cursor, "model_name", testString.getBytes());
+ assertColumnBlobData(cursor, "software_version", testString.getBytes());
+ assertColumnBlobData(cursor, "hardware_version", testString.getBytes());
+ assertColumnBlobData(cursor, "companion_app", testString.getBytes());
+ assertColumnBlobData(cursor, "main_icon", testString.getBytes());
+ assertColumnBlobData(cursor, "is_untethered_headset", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_left_icon", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_right_icon", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_case_icon", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_left_battery", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_right_battery", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_case_battery", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_left_charging", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_right_charging", testString.getBytes());
+ assertColumnBlobData(cursor, "untethered_case_charging", testString.getBytes());
+ }
+ }
+
+ @Test
+ public void testDatabaseMigration_103_104() throws IOException {
+ String testString = "TEST STRING";
+
+ // Create a database with version 103
+ SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 103);
+
+ // insert a device to the database
+ ContentValues device = new ContentValues();
+ device.put("address", TEST_BT_ADDR);
+ device.put("migrated", false);
+ device.put("a2dpSupportsOptionalCodecs", -1);
+ device.put("a2dpOptionalCodecsEnabled", -1);
+ device.put("a2dp_connection_policy", 100);
+ device.put("a2dp_sink_connection_policy", 100);
+ device.put("hfp_connection_policy", 100);
+ device.put("hfp_client_connection_policy", 100);
+ device.put("hid_host_connection_policy", 100);
+ device.put("pan_connection_policy", 100);
+ device.put("pbap_connection_policy", 100);
+ device.put("pbap_client_connection_policy", 100);
+ device.put("map_connection_policy", 100);
+ device.put("sap_connection_policy", 100);
+ device.put("hearing_aid_connection_policy", 100);
+ device.put("map_client_connection_policy", 100);
+ device.put("manufacturer_name", testString);
+ device.put("model_name", testString);
+ device.put("software_version", testString);
+ device.put("hardware_version", testString);
+ device.put("companion_app", testString);
+ device.put("main_icon", testString);
+ device.put("is_untethered_headset", testString);
+ device.put("untethered_left_icon", testString);
+ device.put("untethered_right_icon", testString);
+ device.put("untethered_case_icon", testString);
+ device.put("untethered_left_battery", testString);
+ device.put("untethered_right_battery", testString);
+ device.put("untethered_case_battery", testString);
+ device.put("untethered_left_charging", testString);
+ device.put("untethered_right_charging", testString);
+ device.put("untethered_case_charging", testString);
+ assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+ CoreMatchers.not(-1));
+
+ // Migrate database from 103 to 104
+ db.close();
+ db = testHelper.runMigrationsAndValidate(DB_NAME, 104, true,
+ MetadataDatabase.MIGRATION_103_104);
+ Cursor cursor = db.query("SELECT * FROM metadata");
+
+ assertHasColumn(cursor, "last_active_time", true);
+ assertHasColumn(cursor, "is_active_a2dp_device", true);
+
+ while (cursor.moveToNext()) {
+ // Check the two new columns were added with their default values
+ assertColumnIntData(cursor, "last_active_time", -1);
+ assertColumnIntData(cursor, "is_active_a2dp_device", 0);
+ }
+ }
+
/**
* Helper function to check whether the database has the expected column
*/
void assertHasColumn(Cursor cursor, String columnName, boolean hasColumn) {
if (hasColumn) {
- Assert.assertThat(cursor.getColumnIndex(columnName), CoreMatchers.not(-1));
+ assertThat(cursor.getColumnIndex(columnName), CoreMatchers.not(-1));
} else {
- Assert.assertThat(cursor.getColumnIndex(columnName), CoreMatchers.is(-1));
+ assertThat(cursor.getColumnIndex(columnName), CoreMatchers.is(-1));
}
}
/**
+ * Helper function to check whether the database has the expected value
+ */
+ void assertColumnIntData(Cursor cursor, String columnName, int value) {
+ assertThat(cursor.getInt(cursor.getColumnIndex(columnName)), CoreMatchers.is(value));
+ }
+
+ /**
* Helper function to check whether the column data type is BLOB
*/
void assertColumnBlob(Cursor cursor, String columnName) {
- Assert.assertThat(cursor.getType(cursor.getColumnIndex(columnName)),
+ assertThat(cursor.getType(cursor.getColumnIndex(columnName)),
CoreMatchers.is(Cursor.FIELD_TYPE_BLOB));
}
@@ -556,7 +952,7 @@
* Helper function to check the BLOB data in a column is expected
*/
void assertColumnBlobData(Cursor cursor, String columnName, byte[] data) {
- Assert.assertThat(cursor.getBlob(cursor.getColumnIndex(columnName)),
+ assertThat(cursor.getBlob(cursor.getColumnIndex(columnName)),
CoreMatchers.is(data));
}
@@ -572,21 +968,23 @@
// Remove local storage
mDatabaseManager.mMetadataCache.remove(LOCAL_STORAGE);
- mDatabase.delete(LOCAL_STORAGE);
+ mDatabaseManager.deleteDatabase(data);
+ // Wait for handler thread finish its task.
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
}
- void testSetGetProfilePriorityCase(boolean stored, int priority, int expectedPriority,
- boolean expectedSetResult) {
+ void testSetGetProfileConnectionPolicyCase(boolean stored, int connectionPolicy,
+ int expectedConnectionPolicy, boolean expectedSetResult) {
if (stored) {
Metadata data = new Metadata(TEST_BT_ADDR);
mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
mDatabase.insert(data);
}
Assert.assertEquals(expectedSetResult,
- mDatabaseManager.setProfilePriority(mTestDevice,
- BluetoothProfile.HEADSET, priority));
- Assert.assertEquals(expectedPriority,
- mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.HEADSET));
+ mDatabaseManager.setProfileConnectionPolicy(mTestDevice,
+ BluetoothProfile.HEADSET, connectionPolicy));
+ Assert.assertEquals(expectedConnectionPolicy,
+ mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.HEADSET));
// Wait for database update
TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
@@ -594,9 +992,8 @@
// Check number of metadata in the database
if (!stored) {
- if (priority != BluetoothProfile.PRIORITY_OFF
- && priority != BluetoothProfile.PRIORITY_ON
- && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) {
+ if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
// Database won't be updated
Assert.assertEquals(0, list.size());
return;
@@ -606,13 +1003,13 @@
// Check whether the device is in database
restartDatabaseManagerHelper();
- Assert.assertEquals(expectedPriority,
- mDatabaseManager.getProfilePriority(mTestDevice, BluetoothProfile.HEADSET));
+ Assert.assertEquals(expectedConnectionPolicy,
+ mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.HEADSET));
- mDatabase.deleteAll();
+ mDatabaseManager.factoryReset();
+ mDatabaseManager.mMetadataCache.clear();
// Wait for clear database
TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
- mDatabaseManager.mMetadataCache.clear();
}
void testSetGetA2dpOptionalCodecsCase(int test, boolean stored, int value, int expectedValue) {
@@ -653,10 +1050,10 @@
mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice));
}
- mDatabase.deleteAll();
+ mDatabaseManager.factoryReset();
+ mDatabaseManager.mMetadataCache.clear();
// Wait for clear database
TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
- mDatabaseManager.mMetadataCache.clear();
}
void testSetGetCustomMetaCase(boolean stored, int key, byte[] value, boolean expectedResult) {
@@ -690,9 +1087,9 @@
Assert.assertArrayEquals(value,
mDatabaseManager.getCustomMeta(mTestDevice, key));
- mDatabase.deleteAll();
+ mDatabaseManager.factoryReset();
+ mDatabaseManager.mMetadataCache.clear();
// Wait for clear database
TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
- mDatabaseManager.mMetadataCache.clear();
}
}
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/103.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/103.json
new file mode 100644
index 0000000..dd0a641
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/103.json
@@ -0,0 +1,226 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 103,
+ "identityHash": "2feccd539474eca8e3dec65a98c8b084",
+ "entities": [
+ {
+ "tableName": "metadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, PRIMARY KEY(`address`))",
+ "fields": [
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "migrated",
+ "columnName": "migrated",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpSupportsOptionalCodecs",
+ "columnName": "a2dpSupportsOptionalCodecs",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpOptionalCodecsEnabled",
+ "columnName": "a2dpOptionalCodecsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+ "columnName": "a2dp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+ "columnName": "a2dp_sink_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+ "columnName": "hfp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+ "columnName": "hfp_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+ "columnName": "hid_host_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+ "columnName": "pan_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+ "columnName": "pbap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+ "columnName": "pbap_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_connection_policy",
+ "columnName": "map_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+ "columnName": "sap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+ "columnName": "hearing_aid_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+ "columnName": "map_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.manufacturer_name",
+ "columnName": "manufacturer_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.model_name",
+ "columnName": "model_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.software_version",
+ "columnName": "software_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.hardware_version",
+ "columnName": "hardware_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.companion_app",
+ "columnName": "companion_app",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_icon",
+ "columnName": "main_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.is_untethered_headset",
+ "columnName": "is_untethered_headset",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_icon",
+ "columnName": "untethered_left_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_icon",
+ "columnName": "untethered_right_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_icon",
+ "columnName": "untethered_case_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_battery",
+ "columnName": "untethered_left_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_battery",
+ "columnName": "untethered_right_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_battery",
+ "columnName": "untethered_case_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_charging",
+ "columnName": "untethered_left_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_charging",
+ "columnName": "untethered_right_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_charging",
+ "columnName": "untethered_case_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+ "columnName": "enhanced_settings_ui_uri",
+ "affinity": "BLOB",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "address"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2feccd539474eca8e3dec65a98c8b084')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/104.json b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/104.json
new file mode 100644
index 0000000..52c27ed
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/104.json
@@ -0,0 +1,238 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 104,
+ "identityHash": "d944677aac93d61a462f5324f4d71207",
+ "entities": [
+ {
+ "tableName": "metadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `last_active_time` INTEGER NOT NULL, `is_active_a2dp_device` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, PRIMARY KEY(`address`))",
+ "fields": [
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "migrated",
+ "columnName": "migrated",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpSupportsOptionalCodecs",
+ "columnName": "a2dpSupportsOptionalCodecs",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpOptionalCodecsEnabled",
+ "columnName": "a2dpOptionalCodecsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "last_active_time",
+ "columnName": "last_active_time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "is_active_a2dp_device",
+ "columnName": "is_active_a2dp_device",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+ "columnName": "a2dp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+ "columnName": "a2dp_sink_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+ "columnName": "hfp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+ "columnName": "hfp_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+ "columnName": "hid_host_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+ "columnName": "pan_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+ "columnName": "pbap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+ "columnName": "pbap_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_connection_policy",
+ "columnName": "map_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+ "columnName": "sap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+ "columnName": "hearing_aid_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+ "columnName": "map_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.manufacturer_name",
+ "columnName": "manufacturer_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.model_name",
+ "columnName": "model_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.software_version",
+ "columnName": "software_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.hardware_version",
+ "columnName": "hardware_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.companion_app",
+ "columnName": "companion_app",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_icon",
+ "columnName": "main_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.is_untethered_headset",
+ "columnName": "is_untethered_headset",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_icon",
+ "columnName": "untethered_left_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_icon",
+ "columnName": "untethered_right_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_icon",
+ "columnName": "untethered_case_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_battery",
+ "columnName": "untethered_left_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_battery",
+ "columnName": "untethered_right_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_battery",
+ "columnName": "untethered_case_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_charging",
+ "columnName": "untethered_left_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_charging",
+ "columnName": "untethered_right_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_charging",
+ "columnName": "untethered_case_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+ "columnName": "enhanced_settings_ui_uri",
+ "affinity": "BLOB",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "address"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd944677aac93d61a462f5324f4d71207')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index f5f9e76..a1c938c 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -117,7 +117,7 @@
mDeviceQueueMap.put(mSingleDevice, new LinkedBlockingQueue<>());
doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
- doReturn(new ParcelUuid[]{BluetoothUuid.HearingAid}).when(mAdapterService)
+ doReturn(new ParcelUuid[]{BluetoothUuid.HEARING_AID}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
}
@@ -217,32 +217,25 @@
@Test
public void testGetSetPriority() {
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
Assert.assertEquals("Initial device priority",
- BluetoothProfile.PRIORITY_UNDEFINED,
- mService.getPriority(mLeftDevice));
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+ mService.getConnectionPolicy(mLeftDevice));
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
Assert.assertEquals("Setting device priority to PRIORITY_OFF",
- BluetoothProfile.PRIORITY_OFF,
- mService.getPriority(mLeftDevice));
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+ mService.getConnectionPolicy(mLeftDevice));
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Assert.assertEquals("Setting device priority to PRIORITY_ON",
- BluetoothProfile.PRIORITY_ON,
- mService.getPriority(mLeftDevice));
-
- when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_AUTO_CONNECT);
- Assert.assertEquals("Setting device priority to PRIORITY_AUTO_CONNECT",
- BluetoothProfile.PRIORITY_AUTO_CONNECT,
- mService.getPriority(mLeftDevice));
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+ mService.getConnectionPolicy(mLeftDevice));
}
/**
@@ -253,43 +246,35 @@
int badPriorityValue = 1024;
int badBondState = 42;
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_NONE, badPriorityValue, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDING, badPriorityValue, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
- testOkToConnectCase(mSingleDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
testOkToConnectCase(mSingleDevice,
BluetoothDevice.BOND_BONDED, badPriorityValue, false);
testOkToConnectCase(mSingleDevice,
- badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mSingleDevice,
- badBondState, BluetoothProfile.PRIORITY_OFF, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mSingleDevice,
- badBondState, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mSingleDevice,
- badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mSingleDevice,
badBondState, badPriorityValue, false);
}
@@ -301,12 +286,14 @@
public void testOutgoingConnectMissingHearingAidUuid() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -328,8 +315,9 @@
// Set the device priority to PRIORITY_OFF so connect() should fail
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Send a connect request
Assert.assertFalse("Connect expected to fail", mService.connect(mLeftDevice));
@@ -342,12 +330,15 @@
public void testOutgoingConnectTimeout() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -361,8 +352,8 @@
mService.getConnectionState(mLeftDevice));
// Verify the connection state broadcast, and that we are in Disconnected state
- verifyConnectionStateIntent(HearingAidStateMachine.sConnectTimeoutMs * 2, mLeftDevice,
- BluetoothProfile.STATE_DISCONNECTED,
+ verifyConnectionStateIntent(HearingAidStateMachine.sConnectTimeoutMs * 2,
+ mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mService.getConnectionState(mLeftDevice));
@@ -377,12 +368,15 @@
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -409,12 +403,15 @@
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -473,12 +470,15 @@
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -590,12 +590,15 @@
public void testCreateStateMachineStackEvents() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -655,12 +658,15 @@
public void testDeleteStateMachineUnbondEvents() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -716,12 +722,15 @@
public void testDeleteStateMachineDisconnectEvents() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
@@ -765,12 +774,15 @@
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_OFF);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
@@ -799,12 +811,15 @@
getHiSyncIdFromNative();
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
generateConnectionMessageFromNative(mRightDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_DISCONNECTED);
@@ -832,10 +847,12 @@
public void firstTimeConnection_shouldConnectToBothDevices() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request for left device
@@ -915,12 +932,15 @@
public void getHiSyncId_afterFirstDeviceConnected() {
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(mLeftDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mRightDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
- when(mDatabaseManager.getProfilePriority(mSingleDevice, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectHearingAid(any(BluetoothDevice.class));
// Send a connect request
@@ -1007,8 +1027,8 @@
// Update the device priority so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEARING_AID))
- .thenReturn(BluetoothProfile.PRIORITY_ON);
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).connectHearingAid(device);
doReturn(true).when(mNativeInterface).disconnectHearingAid(device);
@@ -1076,7 +1096,7 @@
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEARING_AID))
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID))
.thenReturn(priority);
Assert.assertEquals(expected, mService.okToConnect(device));
}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
index f32f96f..d148b66 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
@@ -125,13 +125,9 @@
public void testListenForPhoneState_ServiceAndSignalStrength() {
BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
- verifyNoMoreInteractions(mTelephonyManager);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
}
/**
@@ -142,19 +138,12 @@
public void testListenForPhoneState_ServiceAndSignalStrengthUpdateTurnOffSignalStrengh() {
BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE));
- verifyNoMoreInteractions(mTelephonyManager);
}
/**
@@ -164,18 +153,11 @@
public void testListenForPhoneState_ServiceAndSignalStrengthUpdateTurnOffAll() {
BluetoothDevice device1 = TestUtils.getTestDevice(mAdapter, 1);
mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_NONE);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
- verifyNoMoreInteractions(mTelephonyManager);
}
/**
@@ -189,27 +171,17 @@
BluetoothDevice device2 = TestUtils.getTestDevice(mAdapter, 2);
// Enabling updates from first device should trigger subscription
mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
- verifyNoMoreInteractions(mTelephonyManager);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
// Enabling updates from second device should not trigger the same subscription
mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
- verifyNoMoreInteractions(mTelephonyManager);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
// Disabling updates from first device should not cancel subscription
mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_NONE);
- verifyNoMoreInteractions(mTelephonyManager);
// Disabling updates from second device should cancel subscription
mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_NONE);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
- verifyNoMoreInteractions(mTelephonyManager);
}
/**
@@ -226,34 +198,18 @@
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE));
verifyNoMoreInteractions(mTelephonyManager);
// Partially enabling updates from second device should trigger partial subscription
- mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ mHeadsetPhoneState.listenForPhoneState(device2,
+ PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
- verify(mTelephonyManager).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
- verifyNoMoreInteractions(mTelephonyManager);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
// Partially disabling updates from first device should not cancel all subscription
mHeadsetPhoneState.listenForPhoneState(device1, PhoneStateListener.LISTEN_NONE);
verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
- verify(mTelephonyManager, times(2)).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
- verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_SIGNAL_STRENGTHS));
- verify(mTelephonyManager, times(2)).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
- verifyNoMoreInteractions(mTelephonyManager);
+ verify(mTelephonyManager).listen(
+ any(), eq(PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
// Partially disabling updates from second device should cancel subscription
mHeadsetPhoneState.listenForPhoneState(device2, PhoneStateListener.LISTEN_NONE);
verify(mTelephonyManager, times(3)).listen(any(), eq(PhoneStateListener.LISTEN_NONE));
- verify(mTelephonyManager, times(3)).setRadioIndicationUpdateMode(
- TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
- TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
- verifyNoMoreInteractions(mTelephonyManager);
}
}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
index ab8bdd6..149c64f 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -85,7 +85,7 @@
private static final int START_VR_TIMEOUT_MILLIS = 1000;
private static final int START_VR_TIMEOUT_WAIT_MILLIS = START_VR_TIMEOUT_MILLIS * 3 / 2;
private static final int MAX_HEADSET_CONNECTIONS = 5;
- private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.Handsfree};
+ private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.HFP};
private static final String TEST_PHONE_NUMBER = "1234567890";
private static final String TEST_CALLER_ID = "Test Name";
@@ -164,9 +164,8 @@
mVoiceRecognitionWakeLock =
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "VoiceRecognitionTest");
TestUtils.setAdapterService(mAdapterService);
- doReturn(true).when(mAdapterService).isEnabled();
doReturn(MAX_HEADSET_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices();
- doReturn(new ParcelUuid[]{BluetoothUuid.Handsfree}).when(mAdapterService)
+ doReturn(new ParcelUuid[]{BluetoothUuid.HFP}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// We cannot mock HeadsetObjectsFactory.getInstance() with Mockito.
// Hence we need to use reflection to call a private method to
@@ -280,8 +279,8 @@
public void testConnectFromApi() {
BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
mBondedDevices.add(device);
Assert.assertTrue(mHeadsetService.connect(device));
verify(mObjectsFactory).makeStateMachine(device,
@@ -324,8 +323,8 @@
public void testUnbondDevice_disconnectBeforeUnbond() {
BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
mBondedDevices.add(device);
Assert.assertTrue(mHeadsetService.connect(device));
verify(mObjectsFactory).makeStateMachine(device,
@@ -368,8 +367,8 @@
public void testUnbondDevice_disconnectAfterUnbond() {
BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
mBondedDevices.add(device);
Assert.assertTrue(mHeadsetService.connect(device));
verify(mObjectsFactory).makeStateMachine(device,
@@ -1154,8 +1153,8 @@
private void connectTestDevice(BluetoothDevice device) {
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
// Make device bonded
mBondedDevices.add(device);
// Use connecting event to indicate that device is connecting
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
index 57a9a2f..465b78e 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -65,7 +65,7 @@
@RunWith(AndroidJUnit4.class)
public class HeadsetServiceTest {
private static final int MAX_HEADSET_CONNECTIONS = 5;
- private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.Handsfree};
+ private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.HFP};
private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;
private static final String TEST_PHONE_NUMBER = "1234567890";
@@ -100,9 +100,8 @@
HeadsetObjectsFactory.class);
method.setAccessible(true);
method.invoke(null, mObjectsFactory);
- doReturn(true).when(mAdapterService).isEnabled();
doReturn(MAX_HEADSET_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices();
- doReturn(new ParcelUuid[]{BluetoothUuid.Handsfree}).when(mAdapterService)
+ doReturn(new ParcelUuid[]{BluetoothUuid.HFP}).when(mAdapterService)
.getRemoteUuids(any(BluetoothDevice.class));
// This line must be called to make sure relevant objects are initialized properly
mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -150,7 +149,7 @@
mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder();
Assert.assertNotNull(mHeadsetServiceBinder);
mHeadsetServiceBinder.setForceScoAudio(true);
- // Mock database for getPriority()
+ // Mock database for getConnectionPolicy()
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
}
@@ -194,43 +193,35 @@
int badPriorityValue = 1024;
int badBondState = 42;
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
- BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
- BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
- BluetoothProfile.PRIORITY_ON, false);
- testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE,
- BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE, badPriorityValue,
false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
- BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
- BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
- BluetoothProfile.PRIORITY_ON, false);
- testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING,
- BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING, badPriorityValue,
false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
- BluetoothProfile.PRIORITY_UNDEFINED, true);
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
- BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
- BluetoothProfile.PRIORITY_ON, true);
- testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED,
- BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED, badPriorityValue,
false);
testOkToAcceptConnectionCase(mCurrentDevice, badBondState,
- BluetoothProfile.PRIORITY_UNDEFINED, false);
- testOkToAcceptConnectionCase(mCurrentDevice, badBondState, BluetoothProfile.PRIORITY_OFF,
- false);
- testOkToAcceptConnectionCase(mCurrentDevice, badBondState, BluetoothProfile.PRIORITY_ON,
- false);
+ BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToAcceptConnectionCase(mCurrentDevice, badBondState,
- BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
+ testOkToAcceptConnectionCase(mCurrentDevice, badBondState,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToAcceptConnectionCase(mCurrentDevice, badBondState, badPriorityValue, false);
}
@@ -241,9 +232,9 @@
*/
@Test
public void testConnectDevice_connectDeviceBelowLimit() {
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
@@ -353,9 +344,9 @@
@Test
public void testConnectDevice_connectDeviceAboveLimit() {
ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -405,9 +396,9 @@
*/
@Test
public void testConnectAudio_withOneDevice() {
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
verify(mObjectsFactory).makeStateMachine(mCurrentDevice,
@@ -457,9 +448,9 @@
@Test
public void testConnectAudio_withMultipleDevices() {
ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -533,9 +524,9 @@
@Test
public void testConnectAudio_connectTwoAudioChannelsShouldFail() {
ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -605,9 +596,9 @@
@Test
public void testConnectAudio_firstConnectedAudioDevice() {
ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
doAnswer(invocation -> {
BluetoothDevice[] devicesArray = new BluetoothDevice[connectedDevices.size()];
return connectedDevices.toArray(devicesArray);
@@ -672,9 +663,9 @@
*/
@Test
public void testConnectAudio_deviceDisconnected() {
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
HeadsetCallState headsetCallState =
new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
@@ -729,9 +720,9 @@
HeadsetCallState headsetCallState =
new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
TEST_PHONE_NUMBER, 128, "");
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
// Connect one device
@@ -788,9 +779,9 @@
new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING,
TEST_PHONE_NUMBER, 128, "");
final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>();
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -846,9 +837,9 @@
*/
@Test
public void testSetSilenceMode() {
- when(mDatabaseManager.getProfilePriority(any(BluetoothDevice.class),
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
eq(BluetoothProfile.HEADSET)))
- .thenReturn(BluetoothProfile.PRIORITY_UNDEFINED);
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
for (int i = 0; i < 2; i++) {
mCurrentDevice = TestUtils.getTestDevice(mAdapter, i);
Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
@@ -893,7 +884,7 @@
private void testOkToAcceptConnectionCase(BluetoothDevice device, int bondState, int priority,
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HEADSET))
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET))
.thenReturn(priority);
Assert.assertEquals(expected, mHeadsetService.okToAcceptConnection(device));
}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 576d169..0dcb031 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -112,8 +112,8 @@
InstrumentationRegistry.getTargetContext().getResources());
when(mHeadsetService.getPackageManager()).thenReturn(
InstrumentationRegistry.getContext().getPackageManager());
- when(mHeadsetService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
when(mHeadsetService.getForceScoAudio()).thenReturn(true);
when(mHeadsetService.okToAcceptConnection(any(BluetoothDevice.class))).thenReturn(true);
when(mHeadsetService.isScoAcceptable(any(BluetoothDevice.class))).thenReturn(true);
@@ -832,7 +832,7 @@
public void testAtBiaEvent_initialSubscriptionWithUpdates() {
setUpConnectedState();
verify(mPhoneState).listenForPhoneState(mTestDevice, PhoneStateListener.LISTEN_SERVICE_STATE
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ | PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA,
new HeadsetAgIndicatorEnableState(true, true, false, false), mTestDevice));
@@ -842,7 +842,7 @@
new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA,
new HeadsetAgIndicatorEnableState(false, true, true, false), mTestDevice));
verify(mPhoneState, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).listenForPhoneState(mTestDevice,
- PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+ PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA,
new HeadsetAgIndicatorEnableState(false, true, false, false), mTestDevice));
diff --git a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
index b31c4e2..1ed8fe8 100644
--- a/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -19,6 +19,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.intent.matcher.IntentMatchers;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
@@ -121,8 +122,8 @@
@Test
public void testIncomingPriorityReject() {
// Return false for priority.
- when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_OFF);
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Inject an event for when incoming connection is requested
StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
@@ -151,8 +152,8 @@
@Test
public void testIncomingPriorityAccept() {
// Return true for priority.
- when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// Inject an event for when incoming connection is requested
StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
@@ -197,8 +198,8 @@
@Test
public void testIncomingTimeout() {
// Return true for priority.
- when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// Inject an event for when incoming connection is requested
StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
@@ -236,10 +237,11 @@
*/
@LargeTest
@Test
+ @FlakyTest
public void testInBandRingtone() {
// Return true for priority.
- when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
@@ -374,8 +376,8 @@
/* Utility function: supported AT command should lead to native call */
private void runSupportedVendorAtCommand(String atCommand, int vendorId) {
// Return true for priority.
- when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
@@ -417,8 +419,8 @@
/* utility function: unsupported vendor specific command shall be filtered. */
public void runUnsupportedVendorAtCommand(String atCommand, int vendorId) {
// Return true for priority.
- when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
@@ -462,8 +464,8 @@
private void runSupportedVendorEvent(int vendorId, String vendorEventCode,
String vendorEventArgument) {
// Setup connection state machine to be in connected state
- when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
@@ -519,8 +521,8 @@
public void runUnsupportedVendorEvent(int vendorId, String vendorEventCode,
String vendorEventArgument) {
// Setup connection state machine to be in connected state
- when(mHeadsetClientService.getPriority(any(BluetoothDevice.class))).thenReturn(
- BluetoothProfile.PRIORITY_ON);
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
diff --git a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
index ede535d..c88fb75 100644
--- a/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
@@ -97,48 +97,40 @@
int badPriorityValue = 1024;
int badBondState = 42;
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_NONE, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_NONE, badPriorityValue, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDING, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_BONDING, badPriorityValue, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_UNDEFINED, true);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_OFF, false);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_ON, true);
- testOkToConnectCase(mTestDevice,
- BluetoothDevice.BOND_BONDED, BluetoothProfile.PRIORITY_AUTO_CONNECT, true);
+ BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true);
testOkToConnectCase(mTestDevice,
BluetoothDevice.BOND_BONDED, badPriorityValue, false);
testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_UNDEFINED, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false);
testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_OFF, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false);
testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_ON, false);
- testOkToConnectCase(mTestDevice,
- badBondState, BluetoothProfile.PRIORITY_AUTO_CONNECT, false);
+ badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false);
testOkToConnectCase(mTestDevice,
badBondState, badPriorityValue, false);
// Restore prirority to undefined for this test device
- Assert.assertTrue(mService.setPriority(
- mTestDevice, BluetoothProfile.PRIORITY_UNDEFINED));
+ Assert.assertTrue(mService.setConnectionPolicy(
+ mTestDevice, BluetoothProfile.CONNECTION_POLICY_UNKNOWN));
}
/**
@@ -153,7 +145,7 @@
boolean expected) {
doReturn(bondState).when(mAdapterService).getBondState(device);
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.HID_HOST))
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HID_HOST))
.thenReturn(priority);
// Test when the AdapterService is in non-quiet mode.
diff --git a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
index bc25a11..53061a6 100644
--- a/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
+++ b/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
@@ -24,6 +24,7 @@
import android.content.Context;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -105,7 +106,7 @@
* @param priority - The priority value you want the device to have
*/
private void mockDevicePriority(BluetoothDevice device, int priority) {
- when(mDatabaseManager.getProfilePriority(device, BluetoothProfile.MAP_CLIENT))
+ when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.MAP_CLIENT))
.thenReturn(priority);
}
@@ -124,7 +125,7 @@
Assert.assertNull(mService.getInstanceMap().get(device));
// connect a bluetooth device
- mockDevicePriority(device, BluetoothProfile.PRIORITY_ON);
+ mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Assert.assertTrue(mService.connect(device));
// is the statemachine created
@@ -137,13 +138,14 @@
* Test that a PRIORITY_OFF device is not connected to
*/
@Test
+ @FlakyTest
public void testConnectPriorityOffDevice() {
// make sure there is no statemachine already defined for this device
BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
Assert.assertNull(mService.getInstanceMap().get(device));
// connect a bluetooth device
- mockDevicePriority(device, BluetoothProfile.PRIORITY_OFF);
+ mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
Assert.assertFalse(mService.connect(device));
// is the statemachine created
@@ -171,7 +173,7 @@
// run the test - connect all devices, set their priorities to on
for (BluetoothDevice d : list) {
- mockDevicePriority(d, BluetoothProfile.PRIORITY_ON);
+ mockDevicePriority(d, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Assert.assertTrue(mService.connect(d));
}